Re-implements the locales selection with suggestions and search.
This replaces the initial implementation of a two-step locale selection with a more advanced version, which does suggestions, search, removes locales that already exist in the user preferences. Bug: 25800339 Bug: 26414919 Bug: 26278049 Bug: 26275094 Bug: 26266914 Bug: 26266743 Bug: 26266712 Bug: 26266605 Bug: 26266490 Bug: 26266021 Change-Id: I88944c86e4cae5eaa00b7ae4855887ab11989253
This commit is contained in:
216
core/java/com/android/internal/app/LocaleHelper.java
Normal file
216
core/java/com/android/internal/app/LocaleHelper.java
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.icu.util.ULocale;
|
||||
import android.util.LocaleList;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class implements some handy methods to proces with locales.
|
||||
*/
|
||||
public class LocaleHelper {
|
||||
|
||||
/**
|
||||
* Sentence-case (first character uppercased).
|
||||
*
|
||||
* <p>There is no good API available for this, not even in ICU.
|
||||
* We can revisit this if we get some ICU support later.</p>
|
||||
*
|
||||
* <p>There are currently several tickets requesting this feature:</p>
|
||||
* <ul>
|
||||
* <li>ICU needs to provide an easy way to titlecase only one first letter
|
||||
* http://bugs.icu-project.org/trac/ticket/11729</li>
|
||||
* <li>Add "initial case"
|
||||
* http://bugs.icu-project.org/trac/ticket/8394</li>
|
||||
* <li>Add code for initialCase, toTitlecase don't modify after Lt,
|
||||
* avoid 49Ers, low-level language-specific casing
|
||||
* http://bugs.icu-project.org/trac/ticket/10410</li>
|
||||
* <li>BreakIterator.getFirstInstance: Often you need to titlecase just the first
|
||||
* word, and leave the rest of the string alone. (closed as duplicate)
|
||||
* http://bugs.icu-project.org/trac/ticket/8946</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>A (clunky) option with the current ICU API is:</p>
|
||||
* {{
|
||||
* BreakIterator breakIterator = BreakIterator.getSentenceInstance(locale);
|
||||
* String result = UCharacter.toTitleCase(locale,
|
||||
* source, breakIterator, UCharacter.TITLECASE_NO_LOWERCASE);
|
||||
* }}
|
||||
*
|
||||
* <p>That also means creating BreakIteratos for each locale. Expensive...</p>
|
||||
*
|
||||
* @param str the string to sentence-case.
|
||||
* @param locale the locale used for the case conversion.
|
||||
* @return the string converted to sentence-case.
|
||||
*/
|
||||
public static String toSentenceCase(String str, Locale locale) {
|
||||
if (str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
final int firstCodePointLen = str.offsetByCodePoints(0, 1);
|
||||
return str.substring(0, firstCodePointLen).toUpperCase(locale)
|
||||
+ str.substring(firstCodePointLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a string for locale name search. Does case conversion for now,
|
||||
* but might do more in the future.
|
||||
*
|
||||
* <p>Warning: it is only intended to be used in searches by the locale picker.
|
||||
* Don't use it for other things, it is very limited.</p>
|
||||
*
|
||||
* @param str the string to normalize
|
||||
* @param locale the locale that might be used for certain operations (i.e. case conversion)
|
||||
* @return the string normalized for search
|
||||
*/
|
||||
public static String normalizeForSearch(String str, Locale locale) {
|
||||
// TODO: tbd if it needs to be smarter (real normalization, remove accents, etc.)
|
||||
// If needed we might use case folding and ICU/CLDR's collation-based loose searching.
|
||||
// TODO: decide what should the locale be, the default locale, or the locale of the string.
|
||||
// Uppercase is better than lowercase because of things like sharp S, Greek sigma, ...
|
||||
return str.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale localized for display in the provided locale.
|
||||
*
|
||||
* @param locale the locale whose name is to be displayed.
|
||||
* @param displayLocale the locale in which to display the name.
|
||||
* @param sentenceCase true if the result should be sentence-cased
|
||||
* @return the localized name of the locale.
|
||||
*/
|
||||
public static String getDisplayName(Locale locale, Locale displayLocale, boolean sentenceCase) {
|
||||
String result = ULocale.getDisplayName(locale.toLanguageTag(),
|
||||
ULocale.forLocale(displayLocale));
|
||||
return sentenceCase ? toSentenceCase(result, displayLocale) : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale localized for display in the default locale.
|
||||
*
|
||||
* @param locale the locale whose name is to be displayed.
|
||||
* @param sentenceCase true if the result should be sentence-cased
|
||||
* @return the localized name of the locale.
|
||||
*/
|
||||
public static String getDisplayName(Locale locale, boolean sentenceCase) {
|
||||
String result = ULocale.getDisplayName(locale.toLanguageTag(), ULocale.getDefault());
|
||||
return sentenceCase ? toSentenceCase(result, Locale.getDefault()) : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locale's country localized for display in the provided locale.
|
||||
*
|
||||
* @param locale the locale whose country will be displayed.
|
||||
* @param displayLocale the locale in which to display the name.
|
||||
* @return the localized country name.
|
||||
*/
|
||||
public static String getDisplayCountry(Locale locale, Locale displayLocale) {
|
||||
return ULocale.getDisplayCountry(locale.toLanguageTag(), ULocale.forLocale(displayLocale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locale's country localized for display in the default locale.
|
||||
*
|
||||
* @param locale the locale whose country will be displayed.
|
||||
* @return the localized country name.
|
||||
*/
|
||||
public static String getDisplayCountry(Locale locale) {
|
||||
return ULocale.getDisplayCountry(locale.toLanguageTag(), ULocale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale list localized for display in the provided locale.
|
||||
*
|
||||
* @param locales the list of locales whose names is to be displayed.
|
||||
* @param displayLocale the locale in which to display the names.
|
||||
* If this is null, it will use the default locale.
|
||||
* @return the locale aware list of locale names
|
||||
*/
|
||||
public static String getDisplayLocaleList(LocaleList locales, Locale displayLocale) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
final Locale dispLocale = displayLocale == null ? Locale.getDefault() : displayLocale;
|
||||
int localeCount = locales.size();
|
||||
for (int i = 0; i < localeCount; i++) {
|
||||
Locale locale = locales.get(i);
|
||||
result.append(LocaleHelper.getDisplayName(locale, dispLocale, false));
|
||||
// TODO: language aware list formatter. ICU has one.
|
||||
if (i < localeCount - 1) {
|
||||
result.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the likely subtags for a provided locale ID.
|
||||
*
|
||||
* @param locale the locale to maximize.
|
||||
* @return the maximized Locale instance.
|
||||
*/
|
||||
public static Locale addLikelySubtags(Locale locale) {
|
||||
return libcore.icu.ICU.addLikelySubtags(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale-sensitive comparison for LocaleInfo.
|
||||
*
|
||||
* <p>It uses the label, leaving the decision on what to put there to the LocaleInfo.
|
||||
* For instance fr-CA can be shown as "français" as a generic label in the language selection,
|
||||
* or "français (Canada)" if it is a suggestion, or "Canada" in the country selection.</p>
|
||||
*
|
||||
* <p>Gives priority to suggested locales (to sort them at the top).</p>
|
||||
*/
|
||||
static final class LocaleInfoComparator implements Comparator<LocaleStore.LocaleInfo> {
|
||||
private final Collator mCollator;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param sortLocale the locale to be used for sorting.
|
||||
*/
|
||||
public LocaleInfoComparator(Locale sortLocale) {
|
||||
mCollator = Collator.getInstance(sortLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares its two arguments for order.
|
||||
*
|
||||
* @param lhs the first object to be compared
|
||||
* @param rhs the second object to be compared
|
||||
* @return a negative integer, zero, or a positive integer as the first
|
||||
* argument is less than, equal to, or greater than the second.
|
||||
*/
|
||||
@Override
|
||||
public int compare(LocaleStore.LocaleInfo lhs, LocaleStore.LocaleInfo rhs) {
|
||||
// We don't care about the various suggestion types, just "suggested" (!= 0)
|
||||
// and "all others" (== 0)
|
||||
if (lhs.isSuggested() == rhs.isSuggested()) {
|
||||
// They are in the same "bucket" (suggested / others), so we compare the text
|
||||
return mCollator.compare(lhs.getLabel(), rhs.getLabel());
|
||||
} else {
|
||||
// One locale is suggested and one is not, so we put them in different "buckets"
|
||||
return lhs.isSuggested() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,189 +16,158 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.ListFragment;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.LayoutInflater;
|
||||
import android.util.LocaleList;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
class LocaleAdapter extends ArrayAdapter<LocalePicker.LocaleInfo> {
|
||||
final private Map<String, LocalePicker.LocaleInfo> mLevelOne = new ArrayMap<>();
|
||||
final private Map<String, HashSet<LocalePicker.LocaleInfo>> mLevelTwo = new ArrayMap<>();
|
||||
final private LayoutInflater mInflater;
|
||||
/**
|
||||
* A two-step locale picker. It shows a language, then a country.
|
||||
*
|
||||
* <p>It shows suggestions at the top, then the rest of the locales.
|
||||
* Allows the user to search for locales using both their native name and their name in the
|
||||
* default locale.</p>
|
||||
*/
|
||||
public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener {
|
||||
|
||||
final static class LocaleAwareComparator implements Comparator<LocalePicker.LocaleInfo> {
|
||||
private final Collator mCollator;
|
||||
private SuggestedLocaleAdapter mAdapter;
|
||||
private LocaleSelectedListener mListener;
|
||||
private Set<LocaleStore.LocaleInfo> mLocaleList;
|
||||
private LocaleStore.LocaleInfo mParentLocale;
|
||||
private boolean mTranslatedOnly = false;
|
||||
private boolean mCountryMode = false;
|
||||
|
||||
public LocaleAwareComparator(Locale sortLocale) {
|
||||
mCollator = Collator.getInstance(sortLocale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(LocalePicker.LocaleInfo lhs, LocalePicker.LocaleInfo rhs) {
|
||||
return mCollator.compare(lhs.getLabel(), rhs.getLabel());
|
||||
}
|
||||
/**
|
||||
* Other classes can register to be notified when a locale was selected.
|
||||
*
|
||||
* <p>This is the mechanism to "return" the result of the selection.</p>
|
||||
*/
|
||||
public interface LocaleSelectedListener {
|
||||
/**
|
||||
* The classes that want to retrieve the locale picked should implement this method.
|
||||
* @param locale the locale picked.
|
||||
*/
|
||||
void onLocaleSelected(LocaleStore.LocaleInfo locale);
|
||||
}
|
||||
|
||||
static List<Locale> getCuratedLocaleList(Context context) {
|
||||
final Resources resources = context.getResources();
|
||||
final String[] supportedLocaleCodes = resources.getStringArray(R.array.supported_locales);
|
||||
private static LocalePickerWithRegion createCountryPicker(Context context,
|
||||
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
|
||||
boolean translatedOnly) {
|
||||
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
|
||||
boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
|
||||
true /* country mode */, translatedOnly);
|
||||
return shouldShowTheList ? localePicker : null;
|
||||
}
|
||||
|
||||
final ArrayList<Locale> result = new ArrayList<>(supportedLocaleCodes.length);
|
||||
for (String localeId : supportedLocaleCodes) {
|
||||
Locale locale = Locale.forLanguageTag(localeId);
|
||||
if (!locale.getCountry().isEmpty()) {
|
||||
result.add(Locale.forLanguageTag(localeId));
|
||||
public static LocalePickerWithRegion createLanguagePicker(Context context,
|
||||
LocaleSelectedListener listener, boolean translatedOnly) {
|
||||
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
|
||||
localePicker.setListener(context, listener, null,
|
||||
false /* language mode */, translatedOnly);
|
||||
return localePicker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener and initializes the locale list.
|
||||
*
|
||||
* <p>Returns true if we need to show the list, false if not.</p>
|
||||
*
|
||||
* <p>Can return false because of an error, trying to show a list of countries,
|
||||
* but no parent locale was provided.</p>
|
||||
*
|
||||
* <p>It can also return false if the caller tries to show the list in country mode and
|
||||
* there is only one country available (i.e. Japanese => Japan).
|
||||
* In this case we don't even show the list, we call the listener with that locale,
|
||||
* "pretending" it was selected, and return false.</p>
|
||||
*/
|
||||
private boolean setListener(Context context, LocaleSelectedListener listener,
|
||||
LocaleStore.LocaleInfo parent, boolean countryMode, boolean translatedOnly) {
|
||||
if (countryMode && (parent == null || parent.getLocale() == null)) {
|
||||
// The list of countries is determined as all the countries where the parent language
|
||||
// is used.
|
||||
throw new IllegalArgumentException("The country selection list needs a parent.");
|
||||
}
|
||||
|
||||
this.mCountryMode = countryMode;
|
||||
this.mParentLocale = parent;
|
||||
this.mListener = listener;
|
||||
this.mTranslatedOnly = translatedOnly;
|
||||
setRetainInstance(true);
|
||||
|
||||
final HashSet<String> langTagsToIgnore = new HashSet<>();
|
||||
if (!translatedOnly) {
|
||||
final LocaleList userLocales = LocalePicker.getLocales();
|
||||
final String[] langTags = userLocales.toLanguageTags().split(",");
|
||||
Collections.addAll(langTagsToIgnore, langTags);
|
||||
}
|
||||
|
||||
if (countryMode) {
|
||||
mLocaleList = LocaleStore.getLevelLocales(context,
|
||||
langTagsToIgnore, parent, translatedOnly);
|
||||
if (mLocaleList.size() <= 1) {
|
||||
if (listener != null && (mLocaleList.size() == 1)) {
|
||||
listener.onLocaleSelected(mLocaleList.iterator().next());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
|
||||
null /* no parent */, translatedOnly);
|
||||
}
|
||||
return result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public LocaleAdapter(Context context) {
|
||||
this(context, getCuratedLocaleList(context));
|
||||
}
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
static Locale getBaseLocale(Locale locale) {
|
||||
return new Locale.Builder()
|
||||
.setLocale(locale)
|
||||
.setRegion("")
|
||||
.build();
|
||||
}
|
||||
|
||||
// There is no good API available for this, not even in ICU.
|
||||
// We can revisit this if we get some ICU support later
|
||||
//
|
||||
// There are currently several tickets requesting this feature:
|
||||
// * ICU needs to provide an easy way to titlecase only one first letter
|
||||
// http://bugs.icu-project.org/trac/ticket/11729
|
||||
// * Add "initial case"
|
||||
// http://bugs.icu-project.org/trac/ticket/8394
|
||||
// * Add code for initialCase, toTitlecase don't modify after Lt,
|
||||
// avoid 49Ers, low-level language-specific casing
|
||||
// http://bugs.icu-project.org/trac/ticket/10410
|
||||
// * BreakIterator.getFirstInstance: Often you need to titlecase just the first
|
||||
// word, and leave the rest of the string alone. (closed as duplicate)
|
||||
// http://bugs.icu-project.org/trac/ticket/8946
|
||||
//
|
||||
// A (clunky) option with the current ICU API is:
|
||||
// BreakIterator breakIterator = BreakIterator.getSentenceInstance(locale);
|
||||
// String result = UCharacter.toTitleCase(locale,
|
||||
// source, breakIterator, UCharacter.TITLECASE_NO_LOWERCASE);
|
||||
// That also means creating BreakIteratos for each locale. Expensive...
|
||||
private static String toTitleCase(String s, Locale locale) {
|
||||
if (s.length() == 0) {
|
||||
return s;
|
||||
}
|
||||
final int firstCodePointLen = s.offsetByCodePoints(0, 1);
|
||||
return s.substring(0, firstCodePointLen).toUpperCase(locale)
|
||||
+ s.substring(firstCodePointLen);
|
||||
}
|
||||
|
||||
public LocaleAdapter(Context context, List<Locale> locales) {
|
||||
super(context, R.layout.locale_picker_item, R.id.locale);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
for (Locale locale : locales) {
|
||||
Locale baseLocale = getBaseLocale(locale);
|
||||
String language = baseLocale.toLanguageTag();
|
||||
if (!mLevelOne.containsKey(language)) {
|
||||
String label = toTitleCase(baseLocale.getDisplayName(baseLocale), baseLocale);
|
||||
mLevelOne.put(language, new LocalePicker.LocaleInfo(label, baseLocale));
|
||||
}
|
||||
|
||||
final HashSet<LocalePicker.LocaleInfo> subLocales;
|
||||
if (mLevelTwo.containsKey(language)) {
|
||||
subLocales = mLevelTwo.get(language);
|
||||
Locale sortingLocale;
|
||||
if (mCountryMode) {
|
||||
if (mParentLocale == null) {
|
||||
sortingLocale = Locale.getDefault();
|
||||
this.getActivity().setTitle(R.string.country_selection_title);
|
||||
} else {
|
||||
subLocales = new HashSet<>();
|
||||
mLevelTwo.put(language, subLocales);
|
||||
sortingLocale = mParentLocale.getLocale();
|
||||
this.getActivity().setTitle(mParentLocale.getFullNameNative());
|
||||
}
|
||||
String label = locale.getDisplayCountry(locale);
|
||||
subLocales.add(new LocalePicker.LocaleInfo(label, locale));
|
||||
}
|
||||
|
||||
setAdapterLevel(null);
|
||||
}
|
||||
|
||||
public void setAdapterLevel(String parentLocale) {
|
||||
this.clear();
|
||||
|
||||
if (parentLocale == null) {
|
||||
this.addAll(mLevelOne.values());
|
||||
} else {
|
||||
this.addAll(mLevelTwo.get(parentLocale));
|
||||
sortingLocale = Locale.getDefault();
|
||||
this.getActivity().setTitle(R.string.language_selection_title);
|
||||
}
|
||||
|
||||
Locale sortLocale = (parentLocale == null)
|
||||
? Locale.getDefault()
|
||||
: Locale.forLanguageTag(parentLocale);
|
||||
LocaleAwareComparator comparator = new LocaleAwareComparator(sortLocale);
|
||||
this.sort(comparator);
|
||||
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view;
|
||||
TextView text;
|
||||
if (convertView == null) {
|
||||
view = mInflater.inflate(R.layout.locale_picker_item, parent, false);
|
||||
text = (TextView) view.findViewById(R.id.locale);
|
||||
view.setTag(text);
|
||||
} else {
|
||||
view = convertView;
|
||||
text = (TextView) view.getTag();
|
||||
}
|
||||
LocalePicker.LocaleInfo item = getItem(position);
|
||||
text.setText(item.getLabel());
|
||||
text.setTextLocale(item.getLocale());
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalePickerWithRegion extends ListFragment {
|
||||
private static final int LIST_MODE_LANGUAGE = 0;
|
||||
private static final int LIST_MODE_COUNTRY = 1;
|
||||
|
||||
private LocaleAdapter mAdapter;
|
||||
private int mDisplayMode = LIST_MODE_LANGUAGE;
|
||||
|
||||
public static interface LocaleSelectionListener {
|
||||
// You can add any argument if you really need it...
|
||||
public void onLocaleSelected(Locale locale);
|
||||
}
|
||||
|
||||
private LocaleSelectionListener mListener = null;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mAdapter = new LocaleAdapter(getContext());
|
||||
mAdapter.setAdapterLevel(null);
|
||||
mAdapter = new SuggestedLocaleAdapter(mLocaleList, mCountryMode);
|
||||
LocaleHelper.LocaleInfoComparator comp =
|
||||
new LocaleHelper.LocaleInfoComparator(sortingLocale);
|
||||
mAdapter.sort(comp);
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
|
||||
public void setLocaleSelectionListener(LocaleSelectionListener listener) {
|
||||
mListener = listener;
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
int id = menuItem.getItemId();
|
||||
switch (id) {
|
||||
case android.R.id.home:
|
||||
getFragmentManager().popBackStack();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,24 +176,56 @@ public class LocalePickerWithRegion extends ListFragment {
|
||||
getListView().requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Each listener needs to call {@link LocalePicker.updateLocale(Locale)} to actually
|
||||
* change the locale.
|
||||
* <p/>
|
||||
* We don't call {@link LocalePicker.updateLocale(Locale)} automatically, as it halts
|
||||
* the system for a moment and some callers won't want it.
|
||||
*/
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
final Locale locale = ((LocalePicker.LocaleInfo) getListAdapter().getItem(position)).locale;
|
||||
// TODO: handle the back buttons to return to the language list
|
||||
if (mDisplayMode == LIST_MODE_LANGUAGE) {
|
||||
mDisplayMode = LIST_MODE_COUNTRY;
|
||||
mAdapter.setAdapterLevel(locale.toLanguageTag());
|
||||
return;
|
||||
}
|
||||
if (mListener != null) {
|
||||
mListener.onLocaleSelected(locale);
|
||||
final LocaleStore.LocaleInfo locale =
|
||||
(LocaleStore.LocaleInfo) getListAdapter().getItem(position);
|
||||
|
||||
if (mCountryMode || locale.getParent() != null) {
|
||||
if (mListener != null) {
|
||||
mListener.onLocaleSelected(locale);
|
||||
}
|
||||
getFragmentManager().popBackStack("localeListEditor",
|
||||
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
} else {
|
||||
LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
|
||||
getContext(), mListener, locale, mTranslatedOnly /* translate only */);
|
||||
if (selector != null) {
|
||||
getFragmentManager().beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(getId(), selector).addToBackStack(null)
|
||||
.commit();
|
||||
} else {
|
||||
getFragmentManager().popBackStack("localeListEditor",
|
||||
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (!mCountryMode) {
|
||||
inflater.inflate(R.menu.language_selection_list, menu);
|
||||
|
||||
MenuItem mSearchMenuItem = menu.findItem(R.id.locale_search_menu);
|
||||
SearchView mSearchView = (SearchView) mSearchMenuItem.getActionView();
|
||||
|
||||
mSearchView.setQueryHint(getText(R.string.search_language_hint));
|
||||
mSearchView.setOnQueryTextListener(this);
|
||||
mSearchView.setQuery("", false /* submit */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.getFilter().filter(newText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
287
core/java/com/android/internal/app/LocaleStore.java
Normal file
287
core/java/com/android/internal/app/LocaleStore.java
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IllformedLocaleException;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class LocaleStore {
|
||||
private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
|
||||
private static boolean sFullyInitialized = false;
|
||||
|
||||
public static class LocaleInfo {
|
||||
private static final int SUGGESTION_TYPE_NONE = 0x00;
|
||||
private static final int SUGGESTION_TYPE_SIM = 0x01;
|
||||
|
||||
private final Locale mLocale;
|
||||
private final Locale mParent;
|
||||
private final String mId;
|
||||
private boolean mIsTranslated;
|
||||
private boolean mIsPseudo;
|
||||
private boolean mIsChecked; // Used by the LocaleListEditor to mark entries for deletion
|
||||
// Combination of flags for various reasons to show a locale as a suggestion.
|
||||
// Can be SIM, location, etc.
|
||||
private int mSuggestionFlags;
|
||||
|
||||
private String mFullNameNative;
|
||||
private String mFullCountryNameNative;
|
||||
private String mLangScriptKey;
|
||||
|
||||
private LocaleInfo(Locale locale) {
|
||||
this.mLocale = locale;
|
||||
this.mId = locale.toLanguageTag();
|
||||
this.mParent = getParent(locale);
|
||||
this.mIsChecked = false;
|
||||
this.mSuggestionFlags = SUGGESTION_TYPE_NONE;
|
||||
this.mIsTranslated = false;
|
||||
this.mIsPseudo = false;
|
||||
}
|
||||
|
||||
private LocaleInfo(String localeId) {
|
||||
this(Locale.forLanguageTag(localeId));
|
||||
}
|
||||
|
||||
private static Locale getParent(Locale locale) {
|
||||
if (locale.getCountry().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new Locale.Builder()
|
||||
.setLocale(locale).setRegion("")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return mLocale;
|
||||
}
|
||||
|
||||
public Locale getParent() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public boolean isTranslated() {
|
||||
return mIsTranslated;
|
||||
}
|
||||
|
||||
public void setTranslated(boolean isTranslated) {
|
||||
mIsTranslated = isTranslated;
|
||||
}
|
||||
|
||||
/* package */ boolean isSuggested() {
|
||||
if (!mIsTranslated) { // Never suggest an untranslated locale
|
||||
return false;
|
||||
}
|
||||
return mSuggestionFlags != SUGGESTION_TYPE_NONE;
|
||||
}
|
||||
|
||||
private boolean isSuggestionOfType(int suggestionMask) {
|
||||
return (mSuggestionFlags & suggestionMask) == suggestionMask;
|
||||
}
|
||||
|
||||
public String getFullNameNative() {
|
||||
if (mFullNameNative == null) {
|
||||
mFullNameNative =
|
||||
LocaleHelper.getDisplayName(mLocale, mLocale, true /* sentence case */);
|
||||
}
|
||||
return mFullNameNative;
|
||||
}
|
||||
|
||||
String getFullCountryNameNative() {
|
||||
if (mFullCountryNameNative == null) {
|
||||
mFullCountryNameNative = LocaleHelper.getDisplayCountry(mLocale, mLocale);
|
||||
}
|
||||
return mFullCountryNameNative;
|
||||
}
|
||||
|
||||
/** Returns the name of the locale in the language of the UI.
|
||||
* It is used for search, but never shown.
|
||||
* For instance German will show as "Deutsch" in the list, but we will also search for
|
||||
* "allemand" if the system UI is in French.
|
||||
*/
|
||||
public String getFullNameInUiLanguage() {
|
||||
return LocaleHelper.getDisplayName(mLocale, true /* sentence case */);
|
||||
}
|
||||
|
||||
private String getLangScriptKey() {
|
||||
if (mLangScriptKey == null) {
|
||||
Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(mLocale));
|
||||
mLangScriptKey =
|
||||
(parentWithScript == null)
|
||||
? mLocale.toLanguageTag()
|
||||
: parentWithScript.toLanguageTag();
|
||||
}
|
||||
return mLangScriptKey;
|
||||
}
|
||||
|
||||
String getLabel() {
|
||||
if (getParent() == null || this.isSuggestionOfType(SUGGESTION_TYPE_SIM)) {
|
||||
return getFullNameNative();
|
||||
} else {
|
||||
return getFullCountryNameNative();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getChecked() {
|
||||
return mIsChecked;
|
||||
}
|
||||
|
||||
public void setChecked(boolean checked) {
|
||||
mIsChecked = checked;
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> getSimCountries(Context context) {
|
||||
Set<String> result = new HashSet<>();
|
||||
|
||||
TelephonyManager tm = TelephonyManager.from(context);
|
||||
|
||||
if (tm != null) {
|
||||
String iso = tm.getSimCountryIso().toUpperCase(Locale.US);
|
||||
if (!iso.isEmpty()) {
|
||||
result.add(iso);
|
||||
}
|
||||
|
||||
iso = tm.getNetworkCountryIso().toUpperCase(Locale.US);
|
||||
if (!iso.isEmpty()) {
|
||||
result.add(iso);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void fillCache(Context context) {
|
||||
if (sFullyInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> simCountries = getSimCountries(context);
|
||||
|
||||
for (String localeId : LocalePicker.getSupportedLocales(context)) {
|
||||
if (localeId.isEmpty()) {
|
||||
throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
|
||||
}
|
||||
LocaleInfo li = new LocaleInfo(localeId);
|
||||
if (simCountries.contains(li.getLocale().getCountry())) {
|
||||
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
|
||||
}
|
||||
sLocaleCache.put(li.getId(), li);
|
||||
final Locale parent = li.getParent();
|
||||
if (parent != null) {
|
||||
String parentId = parent.toLanguageTag();
|
||||
if (!sLocaleCache.containsKey(parentId)) {
|
||||
sLocaleCache.put(parentId, new LocaleInfo(parent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
|
||||
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
|
||||
for (String localeId : LocalePicker.getPseudoLocales()) {
|
||||
LocaleInfo li = getLocaleInfo(Locale.forLanguageTag(localeId));
|
||||
if (isInDeveloperMode) {
|
||||
li.setTranslated(true);
|
||||
li.mIsPseudo = true;
|
||||
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
|
||||
} else {
|
||||
sLocaleCache.remove(li.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: See if we can reuse what LocaleList.matchScore does
|
||||
final HashSet<String> localizedLocales = new HashSet<>();
|
||||
for (String localeId : LocalePicker.getSystemAssetLocales()) {
|
||||
LocaleInfo li = new LocaleInfo(localeId);
|
||||
localizedLocales.add(li.getLangScriptKey());
|
||||
}
|
||||
|
||||
for (LocaleInfo li : sLocaleCache.values()) {
|
||||
li.setTranslated(localizedLocales.contains(li.getLangScriptKey()));
|
||||
}
|
||||
|
||||
sFullyInitialized = true;
|
||||
}
|
||||
|
||||
private static int getLevel(Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
|
||||
if (ignorables.contains(li.getId())) return 0;
|
||||
if (li.mIsPseudo) return 2;
|
||||
if (translatedOnly && !li.isTranslated()) return 0;
|
||||
if (li.getParent() != null) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of locales for language or region selection.
|
||||
* If the parent is null, then it is the language list.
|
||||
* If it is not null, then the list will contain all the locales that belong to that perent.
|
||||
* Example: if the parent is "ar", then the region list will contain all Arabic locales.
|
||||
* (this is not language based, but language-script, so that it works for zh-Hant and so on.
|
||||
*/
|
||||
/* package */ static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
|
||||
LocaleInfo parent, boolean translatedOnly) {
|
||||
fillCache(context);
|
||||
String parentId = parent == null ? null : parent.getId();
|
||||
|
||||
HashSet<LocaleInfo> result = new HashSet<>();
|
||||
for (LocaleStore.LocaleInfo li : sLocaleCache.values()) {
|
||||
int level = getLevel(ignorables, li, translatedOnly);
|
||||
if (level == 2) {
|
||||
if (parent != null) { // region selection
|
||||
if (parentId.equals(li.getParent().toLanguageTag())) {
|
||||
if (!li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
|
||||
result.add(li);
|
||||
}
|
||||
}
|
||||
} else { // language selection
|
||||
if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
|
||||
result.add(li);
|
||||
} else {
|
||||
result.add(getLocaleInfo(li.getParent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LocaleInfo getLocaleInfo(Locale locale) {
|
||||
String id = locale.toLanguageTag();
|
||||
LocaleInfo result;
|
||||
if (!sLocaleCache.containsKey(id)) {
|
||||
result = new LocaleInfo(locale);
|
||||
sLocaleCache.put(id, result);
|
||||
} else {
|
||||
result = sLocaleCache.get(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
265
core/java/com/android/internal/app/SuggestedLocaleAdapter.java
Normal file
265
core/java/com/android/internal/app/SuggestedLocaleAdapter.java
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* This adapter wraps around a regular ListAdapter for LocaleInfo, and creates 2 sections.
|
||||
*
|
||||
* <p>The first section contains "suggested" languages (usually including a region),
|
||||
* the second section contains all the languages within the original adapter.
|
||||
* The "others" might still include languages that appear in the "suggested" section.</p>
|
||||
*
|
||||
* <p>Example: if we show "German Switzerland" as "suggested" (based on SIM, let's say),
|
||||
* then "German" will still show in the "others" section, clicking on it will only show the
|
||||
* countries for all the other German locales, but not Switzerland
|
||||
* (Austria, Belgium, Germany, Liechtenstein, Luxembourg)</p>
|
||||
*/
|
||||
class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
|
||||
private static final int TYPE_HEADER_SUGGESTED = 0;
|
||||
private static final int TYPE_HEADER_ALL_OTHERS = 1;
|
||||
private static final int TYPE_LOCALE = 2;
|
||||
|
||||
private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions;
|
||||
private ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
|
||||
private int mSuggestionCount;
|
||||
private final boolean mCountryMode;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
|
||||
mCountryMode = countryMode;
|
||||
mLocaleOptions = new ArrayList<>(localeOptions.size());
|
||||
for (LocaleStore.LocaleInfo li : localeOptions) {
|
||||
if (li.isSuggested()) {
|
||||
mSuggestionCount++;
|
||||
}
|
||||
mLocaleOptions.add(li);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return getItemViewType(position) == TYPE_LOCALE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (!showHeaders()) {
|
||||
return TYPE_LOCALE;
|
||||
} else {
|
||||
if (position == 0) {
|
||||
return TYPE_HEADER_SUGGESTED;
|
||||
}
|
||||
if (position == mSuggestionCount + 1) {
|
||||
return TYPE_HEADER_ALL_OTHERS;
|
||||
}
|
||||
return TYPE_LOCALE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
if (showHeaders()) {
|
||||
return 3; // Two headers in addition to the locales
|
||||
} else {
|
||||
return 1; // Locales items only
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (showHeaders()) {
|
||||
return mLocaleOptions.size() + 2; // 2 extra for the headers
|
||||
} else {
|
||||
return mLocaleOptions.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
int offset = 0;
|
||||
if (showHeaders()) {
|
||||
offset = position > mSuggestionCount ? -2 : -1;
|
||||
}
|
||||
|
||||
return mLocaleOptions.get(position + offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null && mInflater == null) {
|
||||
mInflater = LayoutInflater.from(parent.getContext());
|
||||
}
|
||||
|
||||
int itemType = getItemViewType(position);
|
||||
switch (itemType) {
|
||||
case TYPE_HEADER_SUGGESTED: // intentional fallthrough
|
||||
case TYPE_HEADER_ALL_OTHERS:
|
||||
// Covers both null, and "reusing" a wrong kind of view
|
||||
if (!(convertView instanceof TextView)) {
|
||||
convertView = mInflater.inflate(R.layout.language_picker_section_header,
|
||||
parent, false);
|
||||
}
|
||||
TextView textView = (TextView) convertView;
|
||||
if (itemType == TYPE_HEADER_SUGGESTED) {
|
||||
textView.setText(R.string.language_picker_section_suggested);
|
||||
} else {
|
||||
textView.setText(R.string.language_picker_section_all);
|
||||
}
|
||||
textView.setTextLocale(Locale.getDefault());
|
||||
break;
|
||||
default:
|
||||
// Covers both null, and "reusing" a wrong kind of view
|
||||
if (!(convertView instanceof ViewGroup)) {
|
||||
convertView = mInflater.inflate(R.layout.language_picker_item, parent, false);
|
||||
}
|
||||
|
||||
TextView text = (TextView) convertView.findViewById(R.id.locale);
|
||||
ImageView localized = (ImageView) convertView.findViewById(R.id.l10nWarn);
|
||||
LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
|
||||
text.setText(item.getLabel());
|
||||
if (item.isTranslated() || mCountryMode) {
|
||||
localized.setVisibility(View.GONE);
|
||||
text.setTextLocale(item.getLocale());
|
||||
} else {
|
||||
localized.setVisibility(View.VISIBLE);
|
||||
text.setTextLocale(Locale.getDefault());
|
||||
}
|
||||
}
|
||||
return convertView;
|
||||
}
|
||||
|
||||
|
||||
private boolean showHeaders() {
|
||||
return mSuggestionCount != 0 && mSuggestionCount != mLocaleOptions.size();
|
||||
}
|
||||
|
||||
public void sort(LocaleHelper.LocaleInfoComparator comp) {
|
||||
Collections.sort(mLocaleOptions, comp);
|
||||
}
|
||||
|
||||
class FilterByNativeAndUiNames extends Filter {
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence prefix) {
|
||||
FilterResults results = new FilterResults();
|
||||
|
||||
if (mOriginalLocaleOptions == null) {
|
||||
mOriginalLocaleOptions = new ArrayList<>(mLocaleOptions);
|
||||
}
|
||||
|
||||
ArrayList<LocaleStore.LocaleInfo> values;
|
||||
values = new ArrayList<>(mOriginalLocaleOptions);
|
||||
if (prefix == null || prefix.length() == 0) {
|
||||
results.values = values;
|
||||
results.count = values.size();
|
||||
} else {
|
||||
// TODO: decide if we should use the string's locale
|
||||
Locale locale = Locale.getDefault();
|
||||
String prefixString = LocaleHelper.normalizeForSearch(prefix.toString(), locale);
|
||||
|
||||
final int count = values.size();
|
||||
final ArrayList<LocaleStore.LocaleInfo> newValues = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final LocaleStore.LocaleInfo value = values.get(i);
|
||||
final String nameToCheck = LocaleHelper.normalizeForSearch(
|
||||
value.getFullNameInUiLanguage(), locale);
|
||||
final String nativeNameToCheck = LocaleHelper.normalizeForSearch(
|
||||
value.getFullNameNative(), locale);
|
||||
if (wordMatches(nativeNameToCheck, prefixString)
|
||||
|| wordMatches(nameToCheck, prefixString)) {
|
||||
newValues.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
results.values = newValues;
|
||||
results.count = newValues.size();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// TODO: decide if this is enough, or we want to use a BreakIterator...
|
||||
boolean wordMatches(String valueText, String prefixString) {
|
||||
// First match against the whole, non-splitted value
|
||||
if (valueText.startsWith(prefixString)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String[] words = valueText.split(" ");
|
||||
// Start at index 0, in case valueText starts with space(s)
|
||||
for (String word : words) {
|
||||
if (word.startsWith(prefixString)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
mLocaleOptions = (ArrayList<LocaleStore.LocaleInfo>) results.values;
|
||||
|
||||
mSuggestionCount = 0;
|
||||
for (LocaleStore.LocaleInfo li : mLocaleOptions) {
|
||||
if (li.isSuggested()) {
|
||||
mSuggestionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (results.count > 0) {
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyDataSetInvalidated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return new FilterByNativeAndUiNames();
|
||||
}
|
||||
}
|
||||
50
core/res/res/layout/language_picker_item.xml
Normal file
50
core/res/res/layout/language_picker_item.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="locale"
|
||||
android:textDirection="locale"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/locale"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="France"
|
||||
android:layout_weight="1"
|
||||
android:padding="12dp"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/l10nWarn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@android:drawable/stat_sys_warning"
|
||||
android:focusableInTouchMode="false"
|
||||
android:focusable="false"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:tint="?android:attr/colorAccent"/>
|
||||
|
||||
</LinearLayout>
|
||||
25
core/res/res/layout/language_picker_section_header.xml
Normal file
25
core/res/res/layout/language_picker_section_header.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?android:attr/preferenceCategoryStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:textColor="?android:attr/colorAccent"
|
||||
tools:text="@string/language_picker_section_all"/>
|
||||
25
core/res/res/menu/language_selection_list.xml
Normal file
25
core/res/res/menu/language_selection_list.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/locale_search_menu"
|
||||
android:title="@string/locale_search_menu"
|
||||
app:showAsAction="always"
|
||||
app:actionViewClass="android.widget.SearchView" />
|
||||
|
||||
</menu>
|
||||
@@ -1,21 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 2015 The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<resources>
|
||||
|
||||
<string-array translatable="false" name="supported_locales">
|
||||
<item>af-NA</item> <!-- Afrikaans (Namibia) -->
|
||||
|
||||
@@ -4146,4 +4146,22 @@
|
||||
<string name="user_creation_account_exists"><b><xliff:g id="app" example="Gmail">%1$s</xliff:g></b> is trying to add a new user, but the account <b><xliff:g id="account" example="foobar">%2$s</xliff:g></b> already exists on this device. Proceed anyway?</string>
|
||||
<!-- Message to user that app is trying to create user for a specified account. [CHAR LIMIT=none] -->
|
||||
<string name="user_creation_adding"><b><xliff:g id="app" example="Gmail">%1$s</xliff:g></b> is trying to add a new user for the account <b><xliff:g id="account" example="foobar">%2$s</xliff:g></b>. Proceed?</string>
|
||||
|
||||
<!-- Locale picker strings -->
|
||||
|
||||
<!-- Title for the language selection screen [CHAR LIMIT=25] -->
|
||||
<string name="language_selection_title">Language preference</string>
|
||||
<!-- Title for the region selection screen [CHAR LIMIT=25] -->
|
||||
<string name="country_selection_title">Region preference</string>
|
||||
<!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=20] -->
|
||||
<string name="search_language_hint">Type language name</string>
|
||||
|
||||
<!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] -->
|
||||
<string name="language_picker_section_suggested">Suggested</string>
|
||||
<!-- List section subheader for the language picker, containing a list of all languages available [CHAR LIMIT=30] -->
|
||||
<string name="language_picker_section_all">All languages</string>
|
||||
|
||||
<!-- Menu item in the locale menu [CHAR LIMIT=30] -->
|
||||
<string name="locale_search_menu">Search</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -2473,4 +2473,17 @@
|
||||
<java-symbol type="string" name="status_bar_alarm_clock" />
|
||||
<java-symbol type="string" name="status_bar_secure" />
|
||||
<java-symbol type="string" name="status_bar_clock" />
|
||||
|
||||
<!-- Locale picker -->
|
||||
<java-symbol type="id" name="l10nWarn" />
|
||||
<java-symbol type="id" name="locale_search_menu" />
|
||||
<java-symbol type="layout" name="language_picker_item" />
|
||||
<java-symbol type="layout" name="language_picker_section_header" />
|
||||
<java-symbol type="menu" name="language_selection_list" />
|
||||
<java-symbol type="string" name="country_selection_title" />
|
||||
<java-symbol type="string" name="language_picker_section_all" />
|
||||
<java-symbol type="string" name="language_picker_section_suggested" />
|
||||
<java-symbol type="string" name="language_selection_title" />
|
||||
<java-symbol type="string" name="search_language_hint" />
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user