1 /*
2  * Copyright (C) 2017 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 
17 package androidx.core.os;
18 
19 import android.os.Build;
20 import android.os.LocaleList;
21 
22 import androidx.annotation.IntRange;
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 import androidx.annotation.RequiresApi;
26 import androidx.annotation.Size;
27 
28 import java.util.Locale;
29 
30 /**
31  * Helper for accessing features in {@link LocaleList}.
32  */
33 public final class LocaleListCompat {
34     static final LocaleListInterface IMPL;
35     private static final LocaleListCompat sEmptyLocaleList = new LocaleListCompat();
36 
37 
38     static class LocaleListCompatBaseImpl implements LocaleListInterface {
39         private LocaleListHelper mLocaleList = new LocaleListHelper();
40 
41         @Override
setLocaleList(@onNull Locale... list)42         public void setLocaleList(@NonNull Locale... list) {
43             mLocaleList = new LocaleListHelper(list);
44         }
45 
46         @Override
getLocaleList()47         public Object getLocaleList() {
48             return mLocaleList;
49         }
50 
51         @Override
get(int index)52         public Locale get(int index) {
53             return mLocaleList.get(index);
54         }
55 
56         @Override
isEmpty()57         public boolean isEmpty() {
58             return mLocaleList.isEmpty();
59         }
60 
61         @Override
62         @IntRange(from = 0)
size()63         public int size() {
64             return mLocaleList.size();
65         }
66 
67         @Override
68         @IntRange(from = -1)
indexOf(Locale locale)69         public int indexOf(Locale locale) {
70             return mLocaleList.indexOf(locale);
71         }
72 
73         @Override
equals(Object other)74         public boolean equals(Object other) {
75             return mLocaleList.equals(((LocaleListCompat) other).unwrap());
76         }
77 
78         @Override
hashCode()79         public int hashCode() {
80             return mLocaleList.hashCode();
81         }
82 
83         @Override
toString()84         public String toString() {
85             return mLocaleList.toString();
86         }
87 
88         @Override
toLanguageTags()89         public String toLanguageTags() {
90             return mLocaleList.toLanguageTags();
91         }
92 
93         @Nullable
94         @Override
getFirstMatch(String[] supportedLocales)95         public Locale getFirstMatch(String[] supportedLocales) {
96             if (mLocaleList != null) {
97                 return mLocaleList.getFirstMatch(supportedLocales);
98             }
99             return null;
100         }
101     }
102 
103     @RequiresApi(24)
104     static class LocaleListCompatApi24Impl implements LocaleListInterface {
105         private LocaleList mLocaleList = new LocaleList();
106 
107         @Override
setLocaleList(@onNull Locale... list)108         public void setLocaleList(@NonNull Locale... list) {
109             mLocaleList = new LocaleList(list);
110         }
111 
112         @Override
getLocaleList()113         public Object getLocaleList() {
114             return mLocaleList;
115         }
116 
117         @Override
get(int index)118         public Locale get(int index) {
119             return mLocaleList.get(index);
120         }
121 
122         @Override
isEmpty()123         public boolean isEmpty() {
124             return mLocaleList.isEmpty();
125         }
126 
127         @Override
128         @IntRange(from = 0)
size()129         public int size() {
130             return mLocaleList.size();
131         }
132 
133         @Override
134         @IntRange(from = -1)
indexOf(Locale locale)135         public int indexOf(Locale locale) {
136             return mLocaleList.indexOf(locale);
137         }
138 
139         @Override
equals(Object other)140         public boolean equals(Object other) {
141             return mLocaleList.equals(((LocaleListCompat) other).unwrap());
142         }
143 
144         @Override
hashCode()145         public int hashCode() {
146             return mLocaleList.hashCode();
147         }
148 
149         @Override
toString()150         public String toString() {
151             return mLocaleList.toString();
152         }
153 
154         @Override
toLanguageTags()155         public String toLanguageTags() {
156             return mLocaleList.toLanguageTags();
157         }
158 
159         @Nullable
160         @Override
getFirstMatch(String[] supportedLocales)161         public Locale getFirstMatch(String[] supportedLocales) {
162             if (mLocaleList != null) {
163                 return mLocaleList.getFirstMatch(supportedLocales);
164             }
165             return null;
166         }
167     }
168 
169     static {
170         if (Build.VERSION.SDK_INT >= 24) {
171             IMPL = new LocaleListCompatApi24Impl();
172         } else {
173             IMPL = new LocaleListCompatBaseImpl();
174         }
175     }
176 
LocaleListCompat()177     private LocaleListCompat() {}
178 
179     /**
180      * Creates a new instance of {@link LocaleListCompat} from the Locale list.
181      */
182     @RequiresApi(24)
wrap(Object object)183     public static LocaleListCompat wrap(Object object) {
184         LocaleListCompat instance = new LocaleListCompat();
185         if (object instanceof LocaleList) {
186             instance.setLocaleList((LocaleList) object);
187 
188         }
189         return instance;
190     }
191 
192     /**
193      * Gets the underlying framework object.
194      *
195      * @return an android.os.LocaleList object if API >= 24 , or {@link Locale} if not.
196      */
197     @Nullable
unwrap()198     public Object unwrap() {
199         return IMPL.getLocaleList();
200     }
201 
202     /**
203      * Creates a new instance of {@link LocaleListCompat} from the {@link Locale} array.
204      */
create(@onNull Locale... localeList)205     public static LocaleListCompat create(@NonNull Locale... localeList) {
206         LocaleListCompat instance = new LocaleListCompat();
207         instance.setLocaleListArray(localeList);
208         return instance;
209     }
210 
211     /**
212      * Retrieves the {@link Locale} at the specified index.
213      *
214      * @param index The position to retrieve.
215      * @return The {@link Locale} in the given index
216      */
get(int index)217     public Locale get(int index) {
218         return IMPL.get(index);
219     }
220 
221     /**
222      * Returns whether the {@link LocaleListCompat} contains no {@link Locale} items.
223      *
224      * @return {@code true} if this {@link LocaleListCompat} has no {@link Locale} items,
225      *         {@code false} otherwise
226      */
isEmpty()227     public boolean isEmpty() {
228         return IMPL.isEmpty();
229     }
230 
231     /**
232      * Returns the number of {@link Locale} items in this {@link LocaleListCompat}.
233      */
234     @IntRange(from = 0)
size()235     public int size() {
236         return IMPL.size();
237     }
238 
239     /**
240      * Searches this {@link LocaleListCompat} for the specified {@link Locale} and returns the
241      * index of the first occurrence.
242      *
243      * @param locale The {@link Locale} to search for.
244      * @return The index of the first occurrence of the {@link Locale} or {@code -1} if the item
245      *         wasn't found
246      */
247     @IntRange(from = -1)
indexOf(Locale locale)248     public int indexOf(Locale locale) {
249         return IMPL.indexOf(locale);
250     }
251 
252     /**
253      * Retrieves a String representation of the language tags in this list.
254      */
255     @NonNull
toLanguageTags()256     public String toLanguageTags() {
257         return IMPL.toLanguageTags();
258     }
259 
260     /**
261      * Returns the first match in the locale list given an unordered array of supported locales
262      * in BCP 47 format.
263      *
264      * @return The first {@link Locale} from this list that appears in the given array, or
265      *         {@code null} if the {@link LocaleListCompat} is empty.
266      */
getFirstMatch(String[] supportedLocales)267     public Locale getFirstMatch(String[] supportedLocales) {
268         return IMPL.getFirstMatch(supportedLocales);
269     }
270 
271     /**
272      * Retrieve an empty instance of {@link LocaleList}.
273      */
274     @NonNull
getEmptyLocaleList()275     public static LocaleListCompat getEmptyLocaleList() {
276         return sEmptyLocaleList;
277     }
278 
279     /**
280      * Generates a new LocaleList with the given language tags.
281      *
282      * <p>Note that for API < 24 only the first language tag will be used.</>
283      *
284      * @param list The language tags to be included as a single {@link String} separated by commas.
285      * @return A new instance with the {@link Locale} items identified by the given tags.
286      */
287     @NonNull
forLanguageTags(@ullable String list)288     public static LocaleListCompat forLanguageTags(@Nullable String list) {
289         if (list == null || list.isEmpty()) {
290             return getEmptyLocaleList();
291         } else {
292             final String[] tags = list.split(",", -1);
293             final Locale[] localeArray = new Locale[tags.length];
294             for (int i = 0; i < localeArray.length; i++) {
295                 localeArray[i] = Build.VERSION.SDK_INT >= 21
296                         ? Locale.forLanguageTag(tags[i])
297                         : LocaleHelper.forLanguageTag(tags[i]);
298             }
299             LocaleListCompat instance = new LocaleListCompat();
300             instance.setLocaleListArray(localeArray);
301             return instance;
302         }
303     }
304 
305     /**
306      * Returns the default locale list, adjusted by moving the default locale to its first
307      * position.
308      */
309     @NonNull @Size(min = 1)
getAdjustedDefault()310     public static LocaleListCompat getAdjustedDefault() {
311         if (Build.VERSION.SDK_INT >= 24) {
312             return LocaleListCompat.wrap(LocaleList.getAdjustedDefault());
313         } else {
314             return LocaleListCompat.create(Locale.getDefault());
315         }
316     }
317 
318     /**
319      * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but
320      * not necessarily at the top of the list. The default locale not being at the top of the list
321      * is an indication that the system has set the default locale to one of the user's other
322      * preferred locales, having concluded that the primary preference is not supported but a
323      * secondary preference is.
324      *
325      * <p>Note that for API &gt;= 24 the default LocaleList would change if Locale.setDefault() is
326      * called. This method takes that into account by always checking the output of
327      * Locale.getDefault() and recalculating the default LocaleList if needed.</p>
328      */
329     @NonNull @Size(min = 1)
getDefault()330     public static LocaleListCompat getDefault() {
331         if (Build.VERSION.SDK_INT >= 24) {
332             return LocaleListCompat.wrap(LocaleList.getDefault());
333         } else {
334             return LocaleListCompat.create(Locale.getDefault());
335         }
336     }
337 
338     @Override
equals(Object other)339     public boolean equals(Object other) {
340         return IMPL.equals(other);
341     }
342 
343     @Override
hashCode()344     public int hashCode() {
345         return IMPL.hashCode();
346     }
347 
348     @Override
toString()349     public String toString() {
350         return IMPL.toString();
351     }
352 
353     @RequiresApi(24)
setLocaleList(LocaleList localeList)354     private void setLocaleList(LocaleList localeList) {
355         final int localeListSize = localeList.size();
356         if (localeListSize > 0) {
357             Locale[] localeArrayList = new Locale[localeListSize];
358             for (int i = 0; i < localeListSize; i++) {
359                 localeArrayList[i] = localeList.get(i);
360             }
361             IMPL.setLocaleList(localeArrayList);
362         }
363     }
364 
setLocaleListArray(Locale... localeArrayList)365     private void setLocaleListArray(Locale... localeArrayList) {
366         IMPL.setLocaleList(localeArrayList);
367     }
368 }
369