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