Merge "Support properly setting and getting LocaleLists"

This commit is contained in:
Roozbeh Pournader
2016-01-11 17:48:53 +00:00
committed by Android (Google) Code Review
5 changed files with 227 additions and 14 deletions

View File

@@ -39109,7 +39109,9 @@ package android.util {
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getFirstMatch(java.lang.String[]);
method public java.util.Locale getPrimary();
method public int indexOf(java.util.Locale);
method public boolean isEmpty();
method public static void setDefault(android.util.LocaleList);
method public int size();
method public java.lang.String toLanguageTags();
method public void writeToParcel(android.os.Parcel, int);

View File

@@ -41457,7 +41457,9 @@ package android.util {
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getFirstMatch(java.lang.String[]);
method public java.util.Locale getPrimary();
method public int indexOf(java.util.Locale);
method public boolean isEmpty();
method public static void setDefault(android.util.LocaleList);
method public int size();
method public java.lang.String toLanguageTags();
method public void writeToParcel(android.os.Parcel, int);

View File

@@ -39125,7 +39125,9 @@ package android.util {
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getFirstMatch(java.lang.String[]);
method public java.util.Locale getPrimary();
method public int indexOf(java.util.Locale);
method public boolean isEmpty();
method public static void setDefault(android.util.LocaleList);
method public int size();
method public java.lang.String toLanguageTags();
method public void writeToParcel(android.os.Parcel, int);

View File

@@ -16,6 +16,7 @@
package android.util;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -56,10 +57,21 @@ public final class LocaleList implements Parcelable {
return mList.length == 0;
}
@IntRange(from=0)
public int size() {
return mList.length;
}
@IntRange(from=-1)
public int indexOf(Locale locale) {
for (int i = 0; i < mList.length; i++) {
if (mList[i].equals(locale)) {
return i;
}
}
return -1;
}
@Override
public boolean equals(Object other) {
if (other == this)
@@ -69,7 +81,7 @@ public final class LocaleList implements Parcelable {
final Locale[] otherList = ((LocaleList) other).mList;
if (mList.length != otherList.length)
return false;
for (int i = 0; i < mList.length; ++i) {
for (int i = 0; i < mList.length; i++) {
if (!mList[i].equals(otherList[i]))
return false;
}
@@ -79,7 +91,7 @@ public final class LocaleList implements Parcelable {
@Override
public int hashCode() {
int result = 1;
for (int i = 0; i < mList.length; ++i) {
for (int i = 0; i < mList.length; i++) {
result = 31 * result + mList[i].hashCode();
}
return result;
@@ -89,7 +101,7 @@ public final class LocaleList implements Parcelable {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < mList.length; ++i) {
for (int i = 0; i < mList.length; i++) {
sb.append(mList[i]);
if (i < mList.length - 1) {
sb.append(',');
@@ -150,12 +162,12 @@ public final class LocaleList implements Parcelable {
final Locale[] localeList = new Locale[list.length];
final HashSet<Locale> seenLocales = new HashSet<Locale>();
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.length; ++i) {
for (int i = 0; i < list.length; i++) {
final Locale l = list[i];
if (l == null) {
throw new NullPointerException();
throw new NullPointerException("list[" + i + "] is null");
} else if (seenLocales.contains(l)) {
throw new IllegalArgumentException();
throw new IllegalArgumentException("list[" + i + "] is a repetition");
} else {
final Locale localeClone = (Locale) l.clone();
localeList[i] = localeClone;
@@ -171,6 +183,55 @@ public final class LocaleList implements Parcelable {
}
}
/**
* Constructs a locale list, with the topLocale moved to the front if it already is
* in otherLocales, or added to the front if it isn't.
*
* {@hide}
*/
public LocaleList(@NonNull Locale topLocale, LocaleList otherLocales) {
if (topLocale == null) {
throw new NullPointerException("topLocale is null");
}
final int inputLength = (otherLocales == null) ? 0 : otherLocales.mList.length;
int topLocaleIndex = -1;
for (int i = 0; i < inputLength; i++) {
if (topLocale.equals(otherLocales.mList[i])) {
topLocaleIndex = i;
break;
}
}
final int outputLength = inputLength + (topLocaleIndex == -1 ? 1 : 0);
final Locale[] localeList = new Locale[outputLength];
localeList[0] = (Locale) topLocale.clone();
if (topLocaleIndex == -1) {
// topLocale was not in otherLocales
for (int i = 0; i < inputLength; i++) {
localeList[i + 1] = (Locale) otherLocales.mList[i].clone();
}
} else {
for (int i = 0; i < topLocaleIndex; i++) {
localeList[i + 1] = (Locale) otherLocales.mList[i].clone();
}
for (int i = topLocaleIndex + 1; i < inputLength; i++) {
localeList[i] = (Locale) otherLocales.mList[i].clone();
}
}
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < outputLength; i++) {
sb.append(localeList[i].toLanguageTag());
if (i < outputLength - 1) {
sb.append(',');
}
}
mList = localeList;
mStringRepresentation = sb.toString();
}
public static final Parcelable.Creator<LocaleList> CREATOR
= new Parcelable.Creator<LocaleList>() {
@Override
@@ -196,7 +257,7 @@ public final class LocaleList implements Parcelable {
} else {
final String[] tags = list.split(",");
final Locale[] localeArray = new Locale[tags.length];
for (int i = 0; i < localeArray.length; ++i) {
for (int i = 0; i < localeArray.length; i++) {
localeArray[i] = Locale.forLanguageTag(tags[i]);
}
return new LocaleList(localeArray);
@@ -227,6 +288,7 @@ public final class LocaleList implements Parcelable {
return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
}
@IntRange(from=0, to=1)
private static int matchScore(Locale supported, Locale desired) {
if (supported.equals(desired)) {
return 1; // return early so we don't do unnecessary computation
@@ -330,18 +392,79 @@ public final class LocaleList implements Parcelable {
private final static Object sLock = new Object();
@GuardedBy("sLock")
private static LocaleList sDefaultLocaleList;
private static LocaleList sLastExplicitlySetLocaleList = null;
@GuardedBy("sLock")
private static LocaleList sDefaultLocaleList = null;
@GuardedBy("sLock")
private static Locale sLastDefaultLocale = null;
// TODO: fix this to return the default system locale list once we have that
/**
* The result is guaranteed to include the default Locale returned by Locale.getDefault(), but
* not necessarily at the top of the list. The default locale not being at the top of the list
* is an indication that the system has set the default locale to one of the user's other
* preferred locales, having concluded that the primary preference is not supported but a
* secondary preference is.
*
* Note that the default LocaleList would change if Locale.setDefault() is called. This method
* takes that into account by always checking the output of Locale.getDefault() and adjusting
* the default LocaleList if needed.
*/
@NonNull @Size(min=1)
public static LocaleList getDefault() {
Locale defaultLocale = Locale.getDefault();
final Locale defaultLocale = Locale.getDefault();
synchronized (sLock) {
if (sDefaultLocaleList == null || sDefaultLocaleList.size() != 1
|| !defaultLocale.equals(sDefaultLocaleList.getPrimary())) {
sDefaultLocaleList = new LocaleList(defaultLocale);
if (!defaultLocale.equals(sLastDefaultLocale)) {
sLastDefaultLocale = defaultLocale;
// It's either the first time someone has asked for the default locale list, or
// someone has called Locale.setDefault() since we last set or adjusted the default
// locale list. So let's adjust the locale list.
if (sDefaultLocaleList != null
&& defaultLocale.equals(sDefaultLocaleList.getPrimary())) {
// The default Locale has changed, but it happens to be the first locale in the
// default locale list, so we don't need to construct a new locale list.
return sDefaultLocaleList;
}
sDefaultLocaleList = new LocaleList(defaultLocale, sLastExplicitlySetLocaleList);
}
// sDefaultLocaleList can't be null, since it can't be set to null by
// LocaleList.setDefault(), and if getDefault() is called before a call to
// setDefault(), sLastDefaultLocale would be null and the check above would set
// sDefaultLocaleList.
return sDefaultLocaleList;
}
}
/**
* Also sets the default locale by calling Locale.setDefault() with the first locale in the
* list.
*
* @throws NullPointerException if the input is <code>null</code>.
* @throws IllegalArgumentException if the input is empty.
*/
public static void setDefault(@NonNull @Size(min=1) LocaleList locales) {
setDefault(locales, 0);
}
/**
* This may be used directly by system processes to set the default locale list for apps. For
* such uses, the default locale list would always come from the user preferences, but the
* default locale may have been chosen to be a locale other than the first locale in the locale
* list (based on the locales the app supports).
*
* {@hide}
*/
public static void setDefault(@NonNull @Size(min=1) LocaleList locales, int localeIndex) {
if (locales == null) {
throw new NullPointerException("locales is null");
}
if (locales.isEmpty()) {
throw new IllegalArgumentException("locales is empty");
}
synchronized (sLock) {
sLastDefaultLocale = locales.get(localeIndex);
Locale.setDefault(sLastDefaultLocale);
sLastExplicitlySetLocaleList = locales;
sDefaultLocaleList = locales;
}
return sDefaultLocaleList;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.LocaleList;
import java.util.Locale;
import junit.framework.TestCase;
public class LocaleListTest extends TestCase {
@SmallTest
public void testConstructor() throws Exception {
LocaleList ll;
ll = new LocaleList(Locale.forLanguageTag("fr"), null);
assertEquals("fr", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.getEmptyLocaleList());
assertEquals("fr", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("fr"));
assertEquals("fr", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de"));
assertEquals("fr,de", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de,ja"));
assertEquals("fr,de,ja", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de,fr,ja"));
assertEquals("fr,de,ja", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de,fr"));
assertEquals("fr,de", ll.toLanguageTags());
ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("fr,de"));
assertEquals("fr,de", ll.toLanguageTags());
}
@SmallTest
public void testConstructor_nullThrows() throws Exception {
try {
final LocaleList ll = new LocaleList(null, LocaleList.getEmptyLocaleList());
fail("Constructing with locale and locale list should throw with a null locale.");
} catch (Throwable e) {
assertEquals(NullPointerException.class, e.getClass());
}
}
@SmallTest
public void testGetDefault_localeSetDefaultCalledButNoChangeNecessary() throws Exception {
final Locale originalLocale = Locale.getDefault();
final LocaleList originalLocaleList = LocaleList.getDefault();
final int originalLocaleIndex = originalLocaleList.indexOf(originalLocale);
// This simulates a situation potentially set by the system processes
LocaleList.setDefault(LocaleList.forLanguageTags("ae,en,ja"), 1 /* en */);
// check our assumptions about input
assertEquals("en", Locale.getDefault().toLanguageTag());
final LocaleList firstResult = LocaleList.getDefault();
assertEquals("ae,en,ja", LocaleList.getDefault().toLanguageTags());
Locale.setDefault(Locale.forLanguageTag("ae"));
assertSame(firstResult, LocaleList.getDefault());
// restore the original values
LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
}
}