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