101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu/* 201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Copyright (C) 2017 The Android Open Source Project 301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Licensed under the Apache License, Version 2.0 (the "License"); 501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * you may not use this file except in compliance with the License. 601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * You may obtain a copy of the License at 701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * http://www.apache.org/licenses/LICENSE-2.0 901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 1001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Unless required by applicable law or agreed to in writing, software 1101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * distributed under the License is distributed on an "AS IS" BASIS, 1201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * See the License for the specific language governing permissions and 1401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * limitations under the License. 1501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 1601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.core.os; 1801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 2001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 2101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanuimport android.os.Build; 229dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikas 23ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.GuardedBy; 24ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.IntRange; 25ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull; 26ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Nullable; 27ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RestrictTo; 28ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Size; 2901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 3001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanuimport java.util.Arrays; 3101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanuimport java.util.Collection; 3201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanuimport java.util.HashSet; 3301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanuimport java.util.Locale; 3401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 3501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu/** 3601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * LocaleListHelper is an immutable list of Locales, typically used to keep an ordered list of user 3701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * preferences for locales. 3801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 3901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 4001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 4101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu@RestrictTo(LIBRARY_GROUP) 4201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanufinal class LocaleListHelper { 4301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private final Locale[] mList; 4401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // This is a comma-separated list of the locales in the LocaleListHelper created at construction 4501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // time, basically the result of running each locale's toLanguageTag() method and concatenating 4601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // them with commas in between. 4701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @NonNull 4801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private final String mStringRepresentation; 4901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 5001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final Locale[] sEmptyList = new Locale[0]; 5101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final LocaleListHelper sEmptyLocaleList = new LocaleListHelper(); 5201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 5301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 5401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Retrieves the {@link Locale} at the specified index. 5501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 5601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @param index The position to retrieve. 5701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @return The {@link Locale} in the given index. 5801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 5901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 6001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 6101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu Locale get(int index) { 6201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return (0 <= index && index < mList.length) ? mList[index] : null; 6301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 6401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 6501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 6601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Returns whether the {@link LocaleListHelper} contains no {@link Locale} items. 6701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 6801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @return {@code true} if this {@link LocaleListHelper} has no {@link Locale} items, 6901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * {@code false} otherwise. 7001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 7101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 7201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 7301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu boolean isEmpty() { 7401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return mList.length == 0; 7501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 7601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 7701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 7801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Returns the number of {@link Locale} items in this {@link LocaleListHelper}. 7901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 8001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 8101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 8201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @IntRange(from = 0) 8301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int size() { 8401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return mList.length; 8501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 8601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 8701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 8801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Searches this {@link LocaleListHelper} for the specified {@link Locale} and returns the index 8901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * of the first occurrence. 9001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 9101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @param locale The {@link Locale} to search for. 9201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @return The index of the first occurrence of the {@link Locale} or {@code -1} if the item 9301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * wasn't found. 9401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 9501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 9601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 9701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @IntRange(from = -1) 9801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int indexOf(Locale locale) { 9901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < mList.length; i++) { 10001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (mList[i].equals(locale)) { 10101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return i; 10201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 10301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 10401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return -1; 10501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 10601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 10701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @Override 10801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu public boolean equals(Object other) { 10901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (other == this) { 11001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return true; 11101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 11201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (!(other instanceof LocaleListHelper)) { 11301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return false; 11401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 11501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale[] otherList = ((LocaleListHelper) other).mList; 11601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (mList.length != otherList.length) { 11701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return false; 11801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 11901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < mList.length; i++) { 12001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (!mList[i].equals(otherList[i])) { 12101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return false; 12201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 12301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 12401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return true; 12501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 12601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 12701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @Override 12801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu public int hashCode() { 12901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int result = 1; 13001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < mList.length; i++) { 13101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu result = 31 * result + mList[i].hashCode(); 13201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 13301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return result; 13401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 13501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 13601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @Override 13701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu public String toString() { 13801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu StringBuilder sb = new StringBuilder(); 13901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append("["); 14001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < mList.length; i++) { 14101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append(mList[i]); 14201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (i < mList.length - 1) { 14301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append(','); 14401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 14501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 14601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append("]"); 14701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return sb.toString(); 14801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 14901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 15001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 15101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Retrieves a String representation of the language tags in this list. 15201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 15301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 15401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 15501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @NonNull 15601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu String toLanguageTags() { 15701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return mStringRepresentation; 15801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 15901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 16001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 16101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Creates a new {@link LocaleListHelper}. 16201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 16301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * <p>For empty lists of {@link Locale} items it is better to use {@link #getEmptyLocaleList()}, 16401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * which returns a pre-constructed empty list.</p> 16501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 16601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @throws NullPointerException if any of the input locales is <code>null</code>. 16701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @throws IllegalArgumentException if any of the input locales repeat. 16801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 16901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 17001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 17101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 17201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu LocaleListHelper(@NonNull Locale... list) { 17301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (list.length == 0) { 17401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu mList = sEmptyList; 17501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu mStringRepresentation = ""; 17601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 17701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale[] localeList = new Locale[list.length]; 17801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final HashSet<Locale> seenLocales = new HashSet<Locale>(); 17901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final StringBuilder sb = new StringBuilder(); 18001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < list.length; i++) { 18101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale l = list[i]; 18201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (l == null) { 18301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu throw new NullPointerException("list[" + i + "] is null"); 18401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else if (seenLocales.contains(l)) { 18501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu throw new IllegalArgumentException("list[" + i + "] is a repetition"); 18601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 18701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale localeClone = (Locale) l.clone(); 18801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu localeList[i] = localeClone; 18901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append(LocaleHelper.toLanguageTag(localeClone)); 19001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (i < list.length - 1) { 19101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append(','); 19201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 19301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu seenLocales.add(localeClone); 19401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 19501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 19601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu mList = localeList; 19701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu mStringRepresentation = sb.toString(); 19801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 19901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 20001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 20101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 20201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Constructs a locale list, with the topLocale moved to the front if it already is 20301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * in otherLocales, or added to the front if it isn't. 20401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 20501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 20601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 20701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 20801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu LocaleListHelper(@NonNull Locale topLocale, LocaleListHelper otherLocales) { 20901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (topLocale == null) { 21001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu throw new NullPointerException("topLocale is null"); 21101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 21201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 21301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final int inputLength = (otherLocales == null) ? 0 : otherLocales.mList.length; 21401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int topLocaleIndex = -1; 21501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < inputLength; i++) { 21601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (topLocale.equals(otherLocales.mList[i])) { 21701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu topLocaleIndex = i; 21801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu break; 21901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 22001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 22101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 22201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final int outputLength = inputLength + (topLocaleIndex == -1 ? 1 : 0); 22301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale[] localeList = new Locale[outputLength]; 22401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu localeList[0] = (Locale) topLocale.clone(); 22501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (topLocaleIndex == -1) { 22601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // topLocale was not in otherLocales 22701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < inputLength; i++) { 22801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu localeList[i + 1] = (Locale) otherLocales.mList[i].clone(); 22901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 23001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 23101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < topLocaleIndex; i++) { 23201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu localeList[i + 1] = (Locale) otherLocales.mList[i].clone(); 23301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 23401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = topLocaleIndex + 1; i < inputLength; i++) { 23501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu localeList[i] = (Locale) otherLocales.mList[i].clone(); 23601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 23701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 23801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 23901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final StringBuilder sb = new StringBuilder(); 24001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < outputLength; i++) { 24101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append(LocaleHelper.toLanguageTag(localeList[i])); 24201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 24301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (i < outputLength - 1) { 24401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sb.append(','); 24501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 24601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 24701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 24801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu mList = localeList; 24901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu mStringRepresentation = sb.toString(); 25001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 25101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 25201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 25301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Retrieve an empty instance of {@link LocaleListHelper}. 25401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 25501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 25601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 25701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @NonNull 25801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static LocaleListHelper getEmptyLocaleList() { 25901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return sEmptyLocaleList; 26001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 26101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 26201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 26301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Generates a new LocaleListHelper with the given language tags. 26401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 26501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @param list The language tags to be included as a single {@link String} separated by commas. 26601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @return A new instance with the {@link Locale} items identified by the given tags. 26701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 26801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 26901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 27001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 27101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @NonNull 27201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static LocaleListHelper forLanguageTags(@Nullable String list) { 273a952a61ad2cb360aa00285f1ce33fc5bedbf376dAurimas Liutikas if (list == null || list.isEmpty()) { 27401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return getEmptyLocaleList(); 27501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 276244232024786c3715124b4488fd48722f10c22fbAurimas Liutikas final String[] tags = list.split(",", -1); 27701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale[] localeArray = new Locale[tags.length]; 27801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int i = 0; i < localeArray.length; i++) { 27901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu localeArray[i] = LocaleHelper.forLanguageTag(tags[i]); 28001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 28101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return new LocaleListHelper(localeArray); 28201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 28301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 28401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 28501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static String getLikelyScript(Locale locale) { 28601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (Build.VERSION.SDK_INT >= 21) { 28701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final String script = locale.getScript(); 28801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (!script.isEmpty()) { 28901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return script; 29001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 29101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return ""; 29201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 29301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 29401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return ""; 29501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 29601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 29701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final String STRING_EN_XA = "en-XA"; 29801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final String STRING_AR_XB = "ar-XB"; 29901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final Locale LOCALE_EN_XA = new Locale("en", "XA"); 30001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final Locale LOCALE_AR_XB = new Locale("ar", "XB"); 30101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final int NUM_PSEUDO_LOCALES = 2; 30201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 30301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static boolean isPseudoLocale(String locale) { 30401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale); 30501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 30601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 30701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static boolean isPseudoLocale(Locale locale) { 30801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale); 30901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 31001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 31101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @IntRange(from = 0, to = 1) 31201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static int matchScore(Locale supported, Locale desired) { 31301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (supported.equals(desired)) { 31401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 1; // return early so we don't do unnecessary computation 31501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 31601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (!supported.getLanguage().equals(desired.getLanguage())) { 31701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 0; 31801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 31901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (isPseudoLocale(supported) || isPseudoLocale(desired)) { 32001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // The locales are not the same, but the languages are the same, and one of the locales 32101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // is a pseudo-locale. So this is not a match. 32201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 0; 32301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 32401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final String supportedScr = getLikelyScript(supported); 32501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (supportedScr.isEmpty()) { 32601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // If we can't guess a script, we don't know enough about the locales' language to find 32701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // if the locales match. So we fall back to old behavior of matching, which considered 32801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // locales with different regions different. 32901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final String supportedRegion = supported.getCountry(); 33001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return (supportedRegion.isEmpty() || supportedRegion.equals(desired.getCountry())) 33101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu ? 1 33201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu : 0; 33301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 33401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final String desiredScr = getLikelyScript(desired); 33501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // There is no match if the two locales use different scripts. This will most imporantly 33601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // take care of traditional vs simplified Chinese. 33701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return supportedScr.equals(desiredScr) ? 1 : 0; 33801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 33901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 34001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private int findFirstMatchIndex(Locale supportedLocale) { 34101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (int idx = 0; idx < mList.length; idx++) { 34201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final int score = matchScore(supportedLocale, mList[idx]); 34301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (score > 0) { 34401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return idx; 34501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 34601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 34701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return Integer.MAX_VALUE; 34801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 34901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 35001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static final Locale EN_LATN = LocaleHelper.forLanguageTag("en-Latn"); 35101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 35201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private int computeFirstMatchIndex(Collection<String> supportedLocales, 35301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu boolean assumeEnglishIsSupported) { 35401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (mList.length == 1) { // just one locale, perhaps the most common scenario 35501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 0; 35601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 35701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (mList.length == 0) { // empty locale list 35801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return -1; 35901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 36001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 36101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int bestIndex = Integer.MAX_VALUE; 36201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // Try English first, so we can return early if it's in the LocaleListHelper 36301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (assumeEnglishIsSupported) { 36401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final int idx = findFirstMatchIndex(EN_LATN); 36501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (idx == 0) { // We have a match on the first locale, which is good enough 36601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 0; 36701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else if (idx < bestIndex) { 36801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu bestIndex = idx; 36901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 37001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 37101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (String languageTag : supportedLocales) { 37201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale supportedLocale = LocaleHelper.forLanguageTag(languageTag); 37301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // We expect the average length of locale lists used for locale resolution to be 37401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // smaller than three, so it's OK to do this as an O(mn) algorithm. 37501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final int idx = findFirstMatchIndex(supportedLocale); 37601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (idx == 0) { // We have a match on the first locale, which is good enough 37701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 0; 37801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else if (idx < bestIndex) { 37901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu bestIndex = idx; 38001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 38101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 38201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (bestIndex == Integer.MAX_VALUE) { 38301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // no match was found, so we fall back to the first locale in the locale list 38401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return 0; 38501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 38601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return bestIndex; 38701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 38801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 38901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 39001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private Locale computeFirstMatch(Collection<String> supportedLocales, 39101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu boolean assumeEnglishIsSupported) { 39201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int bestIndex = computeFirstMatchIndex(supportedLocales, assumeEnglishIsSupported); 39301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return bestIndex == -1 ? null : mList[bestIndex]; 39401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 39501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 39601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 39701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Returns the first match in the locale list given an unordered array of supported locales 39801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * in BCP 47 format. 39901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 40001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @return The first {@link Locale} from this list that appears in the given array, or 40101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * {@code null} if the {@link LocaleListHelper} is empty. 40201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 40301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 40401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 40501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 40601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @Nullable 40701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu Locale getFirstMatch(String[] supportedLocales) { 40801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return computeFirstMatch(Arrays.asList(supportedLocales), 40901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu false /* assume English is not supported */); 41001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 41101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 41201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 41301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 41401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 41501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 41601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int getFirstMatchIndex(String[] supportedLocales) { 41701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return computeFirstMatchIndex(Arrays.asList(supportedLocales), 41801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu false /* assume English is not supported */); 41901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 42001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 42101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 42201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Same as getFirstMatch(), but with English assumed to be supported, even if it's not. 42301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 42401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 42501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 42601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @Nullable 42701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu Locale getFirstMatchWithEnglishSupported(String[] supportedLocales) { 42801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return computeFirstMatch(Arrays.asList(supportedLocales), 42901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu true /* assume English is supported */); 43001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 43101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 43201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 43301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 43401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 43501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 43601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int getFirstMatchIndexWithEnglishSupported(Collection<String> supportedLocales) { 43701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return computeFirstMatchIndex(supportedLocales, true /* assume English is supported */); 43801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 43901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 44001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 44101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 44201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 44301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 44401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int getFirstMatchIndexWithEnglishSupported(String[] supportedLocales) { 44501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return getFirstMatchIndexWithEnglishSupported(Arrays.asList(supportedLocales)); 44601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 44701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 44801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 44901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Returns true if the collection of locale tags only contains empty locales and pseudolocales. 45001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Assumes that there is no repetition in the input. 45101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 45201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 45301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 45401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static boolean isPseudoLocalesOnly(@Nullable String[] supportedLocales) { 45501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (supportedLocales == null) { 45601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return true; 45701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 45801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 45901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) { 46001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // This is for optimization. Since there's no repetition in the input, if we have more 46101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // than the number of pseudo-locales plus one for the empty string, it's guaranteed 46201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // that we have some meaninful locale in the collection, so the list is not "practically 46301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // empty". 46401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return false; 46501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 46601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu for (String locale : supportedLocales) { 46701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (!locale.isEmpty() && !isPseudoLocale(locale)) { 46801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return false; 46901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 47001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 47101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return true; 47201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 47301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 47401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** Lock for mutable static fields */ 47501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private final static Object sLock = new Object(); 47601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 47701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @GuardedBy("sLock") 47801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static LocaleListHelper sLastExplicitlySetLocaleList = null; 47901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @GuardedBy("sLock") 48001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static LocaleListHelper sDefaultLocaleList = null; 48101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @GuardedBy("sLock") 48201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static LocaleListHelper sDefaultAdjustedLocaleList = null; 48301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @GuardedBy("sLock") 48401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu private static Locale sLastDefaultLocale = null; 48501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 48601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 48701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but 48801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * not necessarily at the top of the list. The default locale not being at the top of the list 48901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * is an indication that the system has set the default locale to one of the user's other 49001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * preferred locales, having concluded that the primary preference is not supported but a 49101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * secondary preference is. 49201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 49301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * <p>Note that the default LocaleListHelper would change if Locale.setDefault() is called. This 49401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * method takes that into account by always checking the output of Locale.getDefault() and 49501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * recalculating the default LocaleListHelper if needed.</p> 49601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 49701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 49801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 49901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 50001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @NonNull @Size(min = 1) 50101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static LocaleListHelper getDefault() { 50201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu final Locale defaultLocale = Locale.getDefault(); 50301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu synchronized (sLock) { 50401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (!defaultLocale.equals(sLastDefaultLocale)) { 50501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sLastDefaultLocale = defaultLocale; 50601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // It's either the first time someone has asked for the default locale list, or 50701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // someone has called Locale.setDefault() since we last set or adjusted the default 50801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // locale list. So let's recalculate the locale list. 50901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (sDefaultLocaleList != null 51001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu && defaultLocale.equals(sDefaultLocaleList.get(0))) { 51101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // The default Locale has changed, but it happens to be the first locale in the 51201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // default locale list, so we don't need to construct a new locale list. 51301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return sDefaultLocaleList; 51401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 51501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sDefaultLocaleList = new LocaleListHelper( 51601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu defaultLocale, sLastExplicitlySetLocaleList); 51701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sDefaultAdjustedLocaleList = sDefaultLocaleList; 51801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 51901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // sDefaultLocaleList can't be null, since it can't be set to null by 52001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // LocaleListHelper.setDefault(), and if getDefault() is called before a call to 52101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // setDefault(), sLastDefaultLocale would be null and the check above would set 52201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu // sDefaultLocaleList. 52301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return sDefaultLocaleList; 52401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 52501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 52601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 52701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 52801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Returns the default locale list, adjusted by moving the default locale to its first 52901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * position. 53001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 53101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @NonNull @Size(min = 1) 53201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static LocaleListHelper getAdjustedDefault() { 53301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu getDefault(); // to recalculate the default locale list, if necessary 53401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu synchronized (sLock) { 53501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu return sDefaultAdjustedLocaleList; 53601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 53701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 53801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 53901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 54001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * Also sets the default locale by calling Locale.setDefault() with the first locale in the 54101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * list. 54201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 54301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @throws NullPointerException if the input is <code>null</code>. 54401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @throws IllegalArgumentException if the input is empty. 54501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 54601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 54701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 54801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 54901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static void setDefault(@NonNull @Size(min = 1) LocaleListHelper locales) { 55001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu setDefault(locales, 0); 55101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 55201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu 55301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu /** 55401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * This may be used directly by system processes to set the default locale list for apps. For 55501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * such uses, the default locale list would always come from the user preferences, but the 55601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * default locale may have been chosen to be a locale other than the first locale in the locale 55701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * list (based on the locales the app supports). 55801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * 55901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu * @hide 56001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu */ 56101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu @RestrictTo(LIBRARY_GROUP) 56201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu static void setDefault(@NonNull @Size(min = 1) LocaleListHelper locales, 56301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu int localeIndex) { 56401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (locales == null) { 56501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu throw new NullPointerException("locales is null"); 56601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 56701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (locales.isEmpty()) { 56801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu throw new IllegalArgumentException("locales is empty"); 56901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 57001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu synchronized (sLock) { 57101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sLastDefaultLocale = locales.get(localeIndex); 57201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu Locale.setDefault(sLastDefaultLocale); 57301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sLastExplicitlySetLocaleList = locales; 57401f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sDefaultLocaleList = locales; 57501f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu if (localeIndex == 0) { 57601f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sDefaultAdjustedLocaleList = sDefaultLocaleList; 57701f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } else { 57801f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sDefaultAdjustedLocaleList = new LocaleListHelper( 57901f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu sLastDefaultLocale, sDefaultLocaleList); 58001f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 58101f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 58201f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu } 58301f7e32cdf00e64181661c34a3190b70ad4c79afAndrei Stingaceanu} 584