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