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; 23bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackbornimport android.content.LocaleProto; 242591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournaderimport android.icu.util.ULocale; 25bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackbornimport android.util.proto.ProtoOutputStream; 26a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 27a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournaderimport com.android.internal.annotations.GuardedBy; 280ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 29834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournaderimport java.util.Arrays; 30834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournaderimport java.util.Collection; 310ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournaderimport java.util.HashSet; 320ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournaderimport java.util.Locale; 330ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 340ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader/** 3566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * LocaleList is an immutable list of Locales, typically used to keep an ordered list of user 3666f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * preferences for locales. 370ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader */ 38789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawapublic final class LocaleList implements Parcelable { 390ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader private final Locale[] mList; 40f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader // This is a comma-separated list of the locales in the LocaleList created at construction time, 41f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader // basically the result of running each locale's toLanguageTag() method and concatenating them 42f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader // with commas in between. 43789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @NonNull 44f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader private final String mStringRepresentation; 45f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader 460ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader private static final Locale[] sEmptyList = new Locale[0]; 47b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader private static final LocaleList sEmptyLocaleList = new LocaleList(); 480ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 4966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 5066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Retrieves the {@link Locale} at the specified index. 5166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 5266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @param index The position to retrieve. 5366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return The {@link Locale} in the given index. 5466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 5566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri public Locale get(int index) { 5666f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri return (0 <= index && index < mList.length) ? mList[index] : null; 570ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 580ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 5966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 6066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Returns whether the {@link LocaleList} contains no {@link Locale} items. 6166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 6266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return {@code true} if this {@link LocaleList} has no {@link Locale} items, {@code false} 6366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * otherwise. 6466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 650ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader public boolean isEmpty() { 660ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader return mList.length == 0; 670ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 680ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 6966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 7066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Returns the number of {@link Locale} items in this {@link LocaleList}. 7166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 722b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @IntRange(from=0) 730ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader public int size() { 740ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader return mList.length; 750ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 760ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader 7766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 7866f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Searches this {@link LocaleList} for the specified {@link Locale} and returns the index of 7966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * the first occurrence. 8066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 8166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @param locale The {@link Locale} to search for. 8266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return The index of the first occurrence of the {@link Locale} or {@code -1} if the item 8366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * wasn't found. 8466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 852b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @IntRange(from=-1) 862b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public int indexOf(Locale locale) { 872b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 882b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (mList[i].equals(locale)) { 892b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return i; 902b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 912b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 922b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return -1; 932b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 942b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 95b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader @Override 96b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public boolean equals(Object other) { 97b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (other == this) 98b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return true; 99b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (!(other instanceof LocaleList)) 100b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return false; 101b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader final Locale[] otherList = ((LocaleList) other).mList; 102b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (mList.length != otherList.length) 103b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return false; 1042b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 105b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (!mList[i].equals(otherList[i])) 106b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return false; 107b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 108b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return true; 109b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 110b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 111b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader @Override 112b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public int hashCode() { 113b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader int result = 1; 1142b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 115b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader result = 31 * result + mList[i].hashCode(); 116b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 117b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return result; 118b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 119b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 120b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader @Override 121b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public String toString() { 122b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader StringBuilder sb = new StringBuilder(); 123b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append("["); 1242b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < mList.length; i++) { 125b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append(mList[i]); 126b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (i < mList.length - 1) { 127b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append(','); 128b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 129b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 130b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader sb.append("]"); 131b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return sb.toString(); 132b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 133b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 134789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 135789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public int describeContents() { 136789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa return 0; 137789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 138789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 139789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 140789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public void writeToParcel(Parcel dest, int parcelableFlags) { 141789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa dest.writeString(mStringRepresentation); 142789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 143789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 14466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 145bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn * Helper to write LocaleList to a protocol buffer output stream. Assumes the parent 146bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn * protobuf has declared the locale as repeated. 147bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn * 148bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn * @param protoOutputStream Stream to write the locale to. 149bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn * @param fieldId Field Id of the Locale as defined in the parent message. 150bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn * @hide 151bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn */ 152bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { 153bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn for (int i = 0; i < mList.length; i++) { 154bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn final Locale locale = mList[i]; 155bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn final long token = protoOutputStream.start(fieldId); 156bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn protoOutputStream.write(LocaleProto.LANGUAGE, locale.getLanguage()); 157bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn protoOutputStream.write(LocaleProto.COUNTRY, locale.getCountry()); 158bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn protoOutputStream.write(LocaleProto.VARIANT, locale.getVariant()); 159bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn protoOutputStream.end(token); 160bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn } 161bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn } 162bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn 163bf5ba6bbcca6e06a70d62d87059f5560581aada5Dianne Hackborn /** 16466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Retrieves a String representation of the language tags in this list. 16566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 166f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader @NonNull 167b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public String toLanguageTags() { 168f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader return mStringRepresentation; 169b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 170b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 171b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader /** 17266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Creates a new {@link LocaleList}. 17366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 17466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * <p>For empty lists of {@link Locale} items it is better to use {@link #getEmptyLocaleList()}, 17566f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * which returns a pre-constructed empty list.</p> 17666f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 1770ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * @throws NullPointerException if any of the input locales is <code>null</code>. 1780ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader * @throws IllegalArgumentException if any of the input locales repeat. 1790ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader */ 18010ea92aefa7051eb432383e0b56e7c44664fd560Raph Levien public LocaleList(@NonNull Locale... list) { 18110ea92aefa7051eb432383e0b56e7c44664fd560Raph Levien if (list.length == 0) { 1820ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader mList = sEmptyList; 183f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader mStringRepresentation = ""; 1840ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } else { 1850ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader final Locale[] localeList = new Locale[list.length]; 1860ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader final HashSet<Locale> seenLocales = new HashSet<Locale>(); 187f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader final StringBuilder sb = new StringBuilder(); 1882b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < list.length; i++) { 1890ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader final Locale l = list[i]; 1900ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader if (l == null) { 1912b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new NullPointerException("list[" + i + "] is null"); 1920ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } else if (seenLocales.contains(l)) { 1932b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new IllegalArgumentException("list[" + i + "] is a repetition"); 1940ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } else { 195f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader final Locale localeClone = (Locale) l.clone(); 196f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader localeList[i] = localeClone; 197f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader sb.append(localeClone.toLanguageTag()); 198f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader if (i < list.length - 1) { 199f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader sb.append(','); 200f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader } 201f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader seenLocales.add(localeClone); 2020ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 2030ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 2040ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader mList = localeList; 205f036ead2a218ffa43697fcaa999b666a4c6d13cfRoozbeh Pournader mStringRepresentation = sb.toString(); 2060ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 2070ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader } 208b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 2092b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 2102b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * Constructs a locale list, with the topLocale moved to the front if it already is 2112b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * in otherLocales, or added to the front if it isn't. 2122b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 2132b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * {@hide} 2142b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 2152b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public LocaleList(@NonNull Locale topLocale, LocaleList otherLocales) { 2162b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (topLocale == null) { 2172b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new NullPointerException("topLocale is null"); 2182b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2192b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2202b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final int inputLength = (otherLocales == null) ? 0 : otherLocales.mList.length; 2212b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader int topLocaleIndex = -1; 2222b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < inputLength; i++) { 2232b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (topLocale.equals(otherLocales.mList[i])) { 2242b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader topLocaleIndex = i; 2252b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader break; 2262b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2272b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2282b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2292b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final int outputLength = inputLength + (topLocaleIndex == -1 ? 1 : 0); 2302b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final Locale[] localeList = new Locale[outputLength]; 2312b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[0] = (Locale) topLocale.clone(); 2322b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (topLocaleIndex == -1) { 2332b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // topLocale was not in otherLocales 2342b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < inputLength; i++) { 2352b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[i + 1] = (Locale) otherLocales.mList[i].clone(); 2362b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2372b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } else { 2382b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < topLocaleIndex; i++) { 2392b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[i + 1] = (Locale) otherLocales.mList[i].clone(); 2402b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2412b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = topLocaleIndex + 1; i < inputLength; i++) { 2422b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader localeList[i] = (Locale) otherLocales.mList[i].clone(); 2432b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2442b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2452b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2462b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final StringBuilder sb = new StringBuilder(); 2472b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < outputLength; i++) { 2482b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sb.append(localeList[i].toLanguageTag()); 2492b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (i < outputLength - 1) { 2502b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sb.append(','); 2512b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2522b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2532b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 2542b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader mList = localeList; 2552b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader mStringRepresentation = sb.toString(); 2562b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 2572b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 258789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public static final Parcelable.Creator<LocaleList> CREATOR 259789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa = new Parcelable.Creator<LocaleList>() { 260789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 261789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public LocaleList createFromParcel(Parcel source) { 262789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa return LocaleList.forLanguageTags(source.readString()); 263789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 264789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 265789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @Override 266789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa public LocaleList[] newArray(int size) { 267789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa return new LocaleList[size]; 268789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa } 269789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa }; 270789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa 27166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 27266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Retrieve an empty instance of {@link LocaleList}. 27366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 274789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @NonNull 275b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public static LocaleList getEmptyLocaleList() { 276b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return sEmptyLocaleList; 277b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 278b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader 27966f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri /** 28066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * Generates a new LocaleList with the given language tags. 28166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * 28266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @param list The language tags to be included as a single {@link String} separated by commas. 28366f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return A new instance with the {@link Locale} items identified by the given tags. 28466f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri */ 285789d8fdbd9509e567cc3669c59e9e2dac2b57270Yohei Yukawa @NonNull 286b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader public static LocaleList forLanguageTags(@Nullable String list) { 287b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader if (list == null || list.equals("")) { 288b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return getEmptyLocaleList(); 289b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } else { 290b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader final String[] tags = list.split(","); 291b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader final Locale[] localeArray = new Locale[tags.length]; 2922b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader for (int i = 0; i < localeArray.length; i++) { 293b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader localeArray[i] = Locale.forLanguageTag(tags[i]); 294b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 295b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader return new LocaleList(localeArray); 296b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 297b46fdd427c5fc2ca69506cad0b35ea805477319dRoozbeh Pournader } 298a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 2992591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader private static String getLikelyScript(Locale locale) { 3002591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader final String script = locale.getScript(); 3012591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (!script.isEmpty()) { 3022591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return script; 3032591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } else { 3042591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader // TODO: Cache the results if this proves to be too slow 3052591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript(); 3062591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3072591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3082591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader 3091c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final String STRING_EN_XA = "en-XA"; 3101c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final String STRING_AR_XB = "ar-XB"; 3111c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final Locale LOCALE_EN_XA = new Locale("en", "XA"); 3121c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final Locale LOCALE_AR_XB = new Locale("ar", "XB"); 3131c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static final int NUM_PSEUDO_LOCALES = 2; 3141c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 3151c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader private static boolean isPseudoLocale(String locale) { 3161c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale); 3171c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 3181c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 319025402c5e96ef346c39bb6d97ddeab7ca955e01dIgor Viarheichyk /** 320025402c5e96ef346c39bb6d97ddeab7ca955e01dIgor Viarheichyk * Returns true if locale is a pseudo-locale, false otherwise. 321025402c5e96ef346c39bb6d97ddeab7ca955e01dIgor Viarheichyk * {@hide} 322025402c5e96ef346c39bb6d97ddeab7ca955e01dIgor Viarheichyk */ 323025402c5e96ef346c39bb6d97ddeab7ca955e01dIgor Viarheichyk public static boolean isPseudoLocale(Locale locale) { 3241c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale); 3251c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 3261c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 3272b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @IntRange(from=0, to=1) 3282591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader private static int matchScore(Locale supported, Locale desired) { 3292591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (supported.equals(desired)) { 3302591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return 1; // return early so we don't do unnecessary computation 3312591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3322591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (!supported.getLanguage().equals(desired.getLanguage())) { 3332591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return 0; 3342591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3351c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader if (isPseudoLocale(supported) || isPseudoLocale(desired)) { 3361c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // The locales are not the same, but the languages are the same, and one of the locales 3371c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // is a pseudo-locale. So this is not a match. 3381c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return 0; 3391c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 3402591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader final String supportedScr = getLikelyScript(supported); 341b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader if (supportedScr.isEmpty()) { 342b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // If we can't guess a script, we don't know enough about the locales' language to find 343b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // if the locales match. So we fall back to old behavior of matching, which considered 344b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // locales with different regions different. 345b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader final String supportedRegion = supported.getCountry(); 346b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader return (supportedRegion.isEmpty() || 347b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader supportedRegion.equals(desired.getCountry())) 348b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader ? 1 : 0; 349b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader } 3502591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader final String desiredScr = getLikelyScript(desired); 351b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // There is no match if the two locales use different scripts. This will most imporantly 352b927c559e1ef8530b08712507f320502627db298Roozbeh Pournader // take care of traditional vs simplified Chinese. 3532591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader return supportedScr.equals(desiredScr) ? 1 : 0; 3542591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3552591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader 356834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private int findFirstMatchIndex(Locale supportedLocale) { 357834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader for (int idx = 0; idx < mList.length; idx++) { 358834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final int score = matchScore(supportedLocale, mList[idx]); 359834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (score > 0) { 360834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return idx; 361834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 362834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 363834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return Integer.MAX_VALUE; 364834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 365834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 366fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader private static final Locale EN_LATN = Locale.forLanguageTag("en-Latn"); 367fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader 368834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private int computeFirstMatchIndex(Collection<String> supportedLocales, 369834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader boolean assumeEnglishIsSupported) { 3702591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (mList.length == 1) { // just one locale, perhaps the most common scenario 371834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 3722591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3732591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader if (mList.length == 0) { // empty locale list 374834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return -1; 3752591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 376834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 3772591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader int bestIndex = Integer.MAX_VALUE; 378834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // Try English first, so we can return early if it's in the LocaleList 379834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (assumeEnglishIsSupported) { 380834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final int idx = findFirstMatchIndex(EN_LATN); 381834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (idx == 0) { // We have a match on the first locale, which is good enough 382834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 383834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } else if (idx < bestIndex) { 384834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader bestIndex = idx; 385fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader } 386834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 387834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader for (String languageTag : supportedLocales) { 388834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final Locale supportedLocale = Locale.forLanguageTag(languageTag); 3892591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader // We expect the average length of locale lists used for locale resolution to be 3902591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader // smaller than three, so it's OK to do this as an O(mn) algorithm. 391834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader final int idx = findFirstMatchIndex(supportedLocale); 392834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (idx == 0) { // We have a match on the first locale, which is good enough 393834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 394834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } else if (idx < bestIndex) { 395834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader bestIndex = idx; 3962591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 3972591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 398834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (bestIndex == Integer.MAX_VALUE) { 399834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // no match was found, so we fall back to the first locale in the locale list 400834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return 0; 4012591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } else { 402834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return bestIndex; 4032591cc863018c3ca55f7725945a670f9f1c5e5fdRoozbeh Pournader } 4048bca69858aefd2326b1bb7ead75796778ed54e93Roozbeh Pournader } 4058bca69858aefd2326b1bb7ead75796778ed54e93Roozbeh Pournader 406834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private Locale computeFirstMatch(Collection<String> supportedLocales, 407834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader boolean assumeEnglishIsSupported) { 408834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader int bestIndex = computeFirstMatchIndex(supportedLocales, assumeEnglishIsSupported); 409834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return bestIndex == -1 ? null : mList[bestIndex]; 410834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 411834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 4121c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader /** 413fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * Returns the first match in the locale list given an unordered array of supported locales 414834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * in BCP 47 format. 415fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * 41666f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * @return The first {@link Locale} from this list that appears in the given array, or 41766f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * {@code null} if the {@link LocaleList} is empty. 418fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader */ 419fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader @Nullable 420fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader public Locale getFirstMatch(String[] supportedLocales) { 421834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return computeFirstMatch(Arrays.asList(supportedLocales), 422834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader false /* assume English is not supported */); 423fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader } 424fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader 425fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader /** 426b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski * {@hide} 427b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski */ 428b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski public int getFirstMatchIndex(String[] supportedLocales) { 429b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski return computeFirstMatchIndex(Arrays.asList(supportedLocales), 430b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski false /* assume English is not supported */); 431b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski } 432b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski 433b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski /** 434fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * Same as getFirstMatch(), but with English assumed to be supported, even if it's not. 435fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader * {@hide} 436fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader */ 437fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader @Nullable 438fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader public Locale getFirstMatchWithEnglishSupported(String[] supportedLocales) { 439834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return computeFirstMatch(Arrays.asList(supportedLocales), 440834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader true /* assume English is supported */); 441834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 442834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 443834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader /** 444834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * {@hide} 445834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader */ 446834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader public int getFirstMatchIndexWithEnglishSupported(Collection<String> supportedLocales) { 447834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return computeFirstMatchIndex(supportedLocales, true /* assume English is supported */); 448fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader } 449fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader 450fb9236cb0c7bdad05ad01b722806edde7385b296Roozbeh Pournader /** 451834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * {@hide} 452834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader */ 453834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader public int getFirstMatchIndexWithEnglishSupported(String[] supportedLocales) { 454834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return getFirstMatchIndexWithEnglishSupported(Arrays.asList(supportedLocales)); 455834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 456834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 457834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader /** 458834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * Returns true if the collection of locale tags only contains empty locales and pseudolocales. 4591c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader * Assumes that there is no repetition in the input. 4601c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader * {@hide} 4611c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader */ 462b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski public static boolean isPseudoLocalesOnly(@Nullable String[] supportedLocales) { 463b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski if (supportedLocales == null) { 464b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski return true; 465b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski } 466b61e405397200f78b1c652143cba7c751df05a00Adam Lesinski 4671c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) { 4681c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // This is for optimization. Since there's no repetition in the input, if we have more 4691c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // than the number of pseudo-locales plus one for the empty string, it's guaranteed 470834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // that we have some meaninful locale in the collection, so the list is not "practically 4711c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader // empty". 4721c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return false; 4731c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4741c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader for (String locale : supportedLocales) { 4751c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader if (!locale.isEmpty() && !isPseudoLocale(locale)) { 4761c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return false; 4771c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4781c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4791c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader return true; 4801c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader } 4811c686f2ce6cbfa3fdb598f452aa31d38f3eb2320Roozbeh Pournader 482a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader private final static Object sLock = new Object(); 483a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 484a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader @GuardedBy("sLock") 4852b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader private static LocaleList sLastExplicitlySetLocaleList = null; 4862b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @GuardedBy("sLock") 4872b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader private static LocaleList sDefaultLocaleList = null; 4882b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader @GuardedBy("sLock") 489834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader private static LocaleList sDefaultAdjustedLocaleList = null; 490834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader @GuardedBy("sLock") 4912b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader private static Locale sLastDefaultLocale = null; 492a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader 4932b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 4942b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but 4952b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * not necessarily at the top of the list. The default locale not being at the top of the list 4962b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * is an indication that the system has set the default locale to one of the user's other 4972b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * preferred locales, having concluded that the primary preference is not supported but a 4982b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * secondary preference is. 4992b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 50066f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * <p>Note that the default LocaleList would change if Locale.setDefault() is called. This 50166f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * method takes that into account by always checking the output of Locale.getDefault() and 50266f6bd32d89a5d6efceb1da10bc111cd16ef8f3fClara Bayarri * recalculating the default LocaleList if needed.</p> 5032b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 504a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader @NonNull @Size(min=1) 505a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader public static LocaleList getDefault() { 5062b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader final Locale defaultLocale = Locale.getDefault(); 507a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader synchronized (sLock) { 5082b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (!defaultLocale.equals(sLastDefaultLocale)) { 5092b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sLastDefaultLocale = defaultLocale; 5102b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // It's either the first time someone has asked for the default locale list, or 5112b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // someone has called Locale.setDefault() since we last set or adjusted the default 512834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader // locale list. So let's recalculate the locale list. 5132b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (sDefaultLocaleList != null 514fee44846376c212114223fc4259382921e6dca7aRoozbeh Pournader && defaultLocale.equals(sDefaultLocaleList.get(0))) { 5152b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // The default Locale has changed, but it happens to be the first locale in the 5162b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // default locale list, so we don't need to construct a new locale list. 5172b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return sDefaultLocaleList; 5182b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5192b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sDefaultLocaleList = new LocaleList(defaultLocale, sLastExplicitlySetLocaleList); 520834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sDefaultAdjustedLocaleList = sDefaultLocaleList; 521a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader } 5222b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // sDefaultLocaleList can't be null, since it can't be set to null by 5232b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // LocaleList.setDefault(), and if getDefault() is called before a call to 5242b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // setDefault(), sLastDefaultLocale would be null and the check above would set 5252b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader // sDefaultLocaleList. 5262b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader return sDefaultLocaleList; 5272b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5282b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5292b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 5302b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 531834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * Returns the default locale list, adjusted by moving the default locale to its first 532834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader * position. 533834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader */ 534834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader @NonNull @Size(min=1) 535834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader public static LocaleList getAdjustedDefault() { 536834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader getDefault(); // to recalculate the default locale list, if necessary 537834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader synchronized (sLock) { 538834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader return sDefaultAdjustedLocaleList; 539834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 540834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 541834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader 542834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader /** 5432b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * Also sets the default locale by calling Locale.setDefault() with the first locale in the 5442b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * list. 5452b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 5462b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * @throws NullPointerException if the input is <code>null</code>. 5472b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * @throws IllegalArgumentException if the input is empty. 5482b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 5492b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public static void setDefault(@NonNull @Size(min=1) LocaleList locales) { 5502b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader setDefault(locales, 0); 5512b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5522b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader 5532b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader /** 5542b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * This may be used directly by system processes to set the default locale list for apps. For 5552b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * such uses, the default locale list would always come from the user preferences, but the 5562b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * default locale may have been chosen to be a locale other than the first locale in the locale 5572b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * list (based on the locales the app supports). 5582b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * 5592b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader * {@hide} 5602b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader */ 5612b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader public static void setDefault(@NonNull @Size(min=1) LocaleList locales, int localeIndex) { 5622b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (locales == null) { 5632b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new NullPointerException("locales is null"); 5642b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5652b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader if (locales.isEmpty()) { 5662b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader throw new IllegalArgumentException("locales is empty"); 5672b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader } 5682b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader synchronized (sLock) { 5692b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sLastDefaultLocale = locales.get(localeIndex); 5702b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader Locale.setDefault(sLastDefaultLocale); 5712b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sLastExplicitlySetLocaleList = locales; 5722b5ab1829476d839c24b06efaa92a6460bef3286Roozbeh Pournader sDefaultLocaleList = locales; 573834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader if (localeIndex == 0) { 574834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sDefaultAdjustedLocaleList = sDefaultLocaleList; 575834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } else { 576834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sDefaultAdjustedLocaleList = new LocaleList( 577834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader sLastDefaultLocale, sDefaultLocaleList); 578834641b3908b21e7054c290e116d76e3d5e6888cRoozbeh Pournader } 579a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader } 580a23748a9ff9ddc8b490fc31752afa9b955d5e156Roozbeh Pournader } 5810ba0d6bb188cf8cd279b0684b70c72323bf1fea2Roozbeh Pournader} 582