LocaleUtils.java revision 102ff0726dad764df741e41766d78fcfb829184a
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.inputmethod;
18
19import com.android.internal.annotations.VisibleForTesting;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.text.TextUtils;
24import android.util.LocaleList;
25
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Locale;
29import java.util.Objects;
30
31public final class LocaleUtils {
32
33    @VisibleForTesting
34    public interface LocaleExtractor<T> {
35        @Nullable
36        Locale get(@Nullable T source);
37    }
38
39    @Nullable
40    private static String getLanguage(@Nullable Locale locale) {
41        if (locale == null) {
42            return null;
43        }
44        return locale.getLanguage();
45    }
46
47    /**
48     * Filters the given items based on language preferences.
49     *
50     * <p>For each language found in {@code preferredLanguages}, this method tries to copy at most
51     * one best-match item from {@code source} to {@code dest}.  For example, if
52     * {@code "en-GB", "ja", "en-AU", "fr-CA", "en-IN"} is specified to {@code preferredLanguages},
53     * this method tries to copy at most one English locale, at most one Japanese, and at most one
54     * French locale from {@code source} to {@code dest}.  Here the best matching English locale
55     * will be searched from {@code source} as follows.
56     * <ol>
57     *     <li>The first instance in {@code sources} that exactly matches {@code "en-GB"}</li>
58     *     <li>The first instance in {@code sources} that exactly matches {@code "en-AU"}</li>
59     *     <li>The first instance in {@code sources} that exactly matches {@code "en-IN"}</li>
60     *     <li>The first instance in {@code sources} that partially matches {@code "en"}</li>
61     * </ol>
62     * <p>Then this method iterates the same algorithm for Japanese then French.</p>
63     *
64     * @param sources Source items to be filtered.
65     * @param extractor Type converter from the source items to {@link Locale} object.
66     * @param preferredLanguages Ordered list of locales with which the input items will be
67     * filtered.
68     * @param dest Destination into which the filtered items will be added.
69     * @param <T> Type of the data items.
70     */
71    @VisibleForTesting
72    public static <T> void filterByLanguage(
73            @NonNull List<T> sources,
74            @NonNull LocaleExtractor<T> extractor,
75            @NonNull LocaleList preferredLanguages,
76            @NonNull ArrayList<T> dest) {
77        final Locale[] availableLocales = new Locale[sources.size()];
78        for (int i = 0; i < availableLocales.length; ++i) {
79            availableLocales[i] = extractor.get(sources.get(i));
80        }
81        final Locale[] sortedPreferredLanguages = new Locale[preferredLanguages.size()];
82        if (sortedPreferredLanguages.length > 0) {
83            int nextIndex = 0;
84            final int N = preferredLanguages.size();
85            languageLoop:
86            for (int i = 0; i < N; ++i) {
87                final String language = getLanguage(preferredLanguages.get(i));
88                for (int j = 0; j < nextIndex; ++j) {
89                    if (TextUtils.equals(getLanguage(sortedPreferredLanguages[j]), language)) {
90                        continue languageLoop;
91                    }
92                }
93                for (int j = i; j < N; ++j) {
94                    final Locale locale = preferredLanguages.get(j);
95                    if (TextUtils.equals(language, getLanguage(locale))) {
96                        sortedPreferredLanguages[nextIndex] = locale;
97                        ++nextIndex;
98                    }
99                }
100            }
101        }
102
103
104        for (int languageIndex = 0; languageIndex < sortedPreferredLanguages.length;) {
105            // Finding the range.
106            final String language = getLanguage(sortedPreferredLanguages[languageIndex]);
107            int nextLanguageIndex = languageIndex;
108            for (; nextLanguageIndex < sortedPreferredLanguages.length; ++nextLanguageIndex) {
109                final Locale locale = sortedPreferredLanguages[nextLanguageIndex];
110                if (!TextUtils.equals(getLanguage(locale), language)) {
111                    break;
112                }
113            }
114
115            // Check exact match
116            boolean found = false;
117            for (int i = languageIndex; !found && i < nextLanguageIndex; ++i) {
118                final Locale locale = sortedPreferredLanguages[i];
119                for (int j = 0; j < availableLocales.length; ++j) {
120                    if (!Objects.equals(locale, availableLocales[j])) {
121                        continue;
122                    }
123                    dest.add(sources.get(j));
124                    found = true;
125                    break;
126                }
127            }
128
129            if (!found) {
130                // No exact match.  Use language match.
131                for (int j = 0; j < availableLocales.length; ++j) {
132                    if (!TextUtils.equals(language, getLanguage(availableLocales[j]))) {
133                        continue;
134                    }
135                    dest.add(sources.get(j));
136                    break;
137                }
138            }
139            languageIndex = nextLanguageIndex;
140        }
141    }
142}