diff --git a/res/values/strings.xml b/res/values/strings.xml
index 141590c15e2..236a66183ce 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -446,6 +446,9 @@
All languages
+
+ More languages
+
All regions
@@ -508,12 +511,20 @@
Search
+
+ Language & region
Add a language
+
+ Choose a region
Region preference
- Type language name
+ Search languages
+
+ Search regions
+
+ The region you choose affects how your phone displays time, dates, temperature, and more
More language settings
diff --git a/res/xml/system_language_picker.xml b/res/xml/system_language_picker.xml
index cccf56ea063..52623928eb4 100644
--- a/res/xml/system_language_picker.xml
+++ b/res/xml/system_language_picker.xml
@@ -16,9 +16,15 @@
+
+
diff --git a/src/com/android/settings/localepicker/LocaleListSearchCallback.java b/src/com/android/settings/localepicker/LocaleListSearchCallback.java
index e24e9bd3fa1..8c6cac76262 100644
--- a/src/com/android/settings/localepicker/LocaleListSearchCallback.java
+++ b/src/com/android/settings/localepicker/LocaleListSearchCallback.java
@@ -16,6 +16,7 @@
package com.android.settings.localepicker;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.app.LocaleStore;
@@ -25,5 +26,6 @@ import java.util.List;
public interface LocaleListSearchCallback {
/** Callback method for searching changes. */
- void onSearchListChanged(@NonNull List localeInfoList);
+ void onSearchListChanged(@NonNull List localeInfoList,
+ @Nullable CharSequence prefix);
}
diff --git a/src/com/android/settings/localepicker/LocalePickerBaseListPreferenceController.java b/src/com/android/settings/localepicker/LocalePickerBaseListPreferenceController.java
index 4b0e9fa4fd4..471ca35e7e1 100644
--- a/src/com/android/settings/localepicker/LocalePickerBaseListPreferenceController.java
+++ b/src/com/android/settings/localepicker/LocalePickerBaseListPreferenceController.java
@@ -60,7 +60,9 @@ public abstract class LocalePickerBaseListPreferenceController extends
private Map mPreferences;
private String mPackageName;
private boolean mIsCountryMode;
- @Nullable private LocaleStore.LocaleInfo mParentLocale;
+ @Nullable
+ private LocaleStore.LocaleInfo mParentLocale;
+ private boolean mIsSuggestedCategory;
public LocalePickerBaseListPreferenceController(@NonNull Context context,
@NonNull String preferenceKey) {
@@ -75,6 +77,7 @@ public abstract class LocalePickerBaseListPreferenceController extends
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceCategory = screen.findPreference(getPreferenceCategoryKey());
+ mIsSuggestedCategory = getPreferenceCategoryKey().contains(KEY_SUGGESTED);
updatePreferences();
}
@@ -88,17 +91,13 @@ public abstract class LocalePickerBaseListPreferenceController extends
mParentLocale = getParentLocale();
if (mParentLocale != null) {
mIsCountryMode = true;
- mLocaleList = getLocaleCollectorController(mContext).getSupportedLocaleList(
- mParentLocale, false, mIsCountryMode);
- mLocaleOptions = new ArrayList<>(mLocaleList.size());
- if (!getPreferenceCategoryKey().contains(KEY_SUGGESTED)) {
+ if (!mIsSuggestedCategory) {
mPreferenceCategory.setTitle(
mContext.getString(R.string.all_supported_locales_regions_title));
}
}
- result = getSortedLocaleList(
- getPreferenceCategoryKey().contains(KEY_SUGGESTED)
+ result = getSortedLocaleList(mIsSuggestedCategory
? getSuggestedLocaleList()
: getSupportedLocaleList());
@@ -112,13 +111,17 @@ public abstract class LocalePickerBaseListPreferenceController extends
}
@Override
- public void onSearchListChanged(@NonNull List newList) {
+ public void onSearchListChanged(@NonNull List newList,
+ @Nullable CharSequence prefix) {
mPreferenceCategory.removeAll();
mPreferences.clear();
final Map existingPreferences = mPreferences;
- if (getPreferenceCategoryKey().contains(KEY_SUGGESTED)) {
- newList = getSortedSuggestedLocaleFromSearchList(
- newList, getSuggestedLocaleList());
+
+ List sortedList =
+ mIsSuggestedCategory ? getSuggestedLocaleList() : getSupportedLocaleList();
+ newList = getSortedSuggestedLocaleFromSearchList(newList, sortedList);
+ if (mIsSuggestedCategory && getParentLocale() != null) {
+ newList = getSortedSuggestedRegionFromSearchList(prefix, newList, sortedList);
}
setupPreference(newList, existingPreferences);
}
@@ -138,6 +141,23 @@ public abstract class LocalePickerBaseListPreferenceController extends
return searchItem;
}
+ private List getSortedSuggestedRegionFromSearchList(
+ @Nullable CharSequence prefix,
+ List listOptions,
+ List listSuggested) {
+ List searchItem = new ArrayList<>();
+ if (prefix == null || prefix.isEmpty()) {
+ return getSortedLocaleList(listSuggested);
+ }
+
+ for (LocaleStore.LocaleInfo option : listOptions) {
+ if (listSuggested.contains(option)) {
+ searchItem.add(option);
+ }
+ }
+ return getSortedLocaleList(searchItem);
+ }
+
private void setupPreference(List localeInfoList,
Map existingPreferences) {
Log.d(TAG, "setupPreference: isNumberingMode = " + isNumberingMode());
@@ -175,18 +195,20 @@ public abstract class LocalePickerBaseListPreferenceController extends
protected abstract LocaleCollectorBase getLocaleCollectorController(Context context);
- @Nullable protected abstract LocaleStore.LocaleInfo getParentLocale();
+ @Nullable
+ protected abstract LocaleStore.LocaleInfo getParentLocale();
protected abstract boolean isNumberingMode();
- @Nullable protected abstract LocaleList getExplicitLocaleList();
+ @Nullable
+ protected abstract LocaleList getExplicitLocaleList();
protected String getPackageName() {
return mPackageName;
}
protected List getSuggestedLocaleList() {
- mLocaleOptions.clear();
+ setupLocaleList();
if (mLocaleList != null && !mLocaleList.isEmpty()) {
mLocaleOptions.addAll(mLocaleList.stream()
.filter(localeInfo -> localeInfo.isSuggested())
@@ -199,6 +221,7 @@ public abstract class LocalePickerBaseListPreferenceController extends
}
protected List getSupportedLocaleList() {
+ setupLocaleList();
if (mLocaleList != null && !mLocaleList.isEmpty()) {
mLocaleOptions.addAll(mLocaleList.stream()
.filter(localeInfo -> !localeInfo.isSuggested())
@@ -206,10 +229,15 @@ public abstract class LocalePickerBaseListPreferenceController extends
} else {
Log.d(TAG, "Can not get supported locales because the locale list is null or empty.");
}
-
return mLocaleOptions;
}
+ private void setupLocaleList() {
+ mLocaleList = getLocaleCollectorController(mContext).getSupportedLocaleList(
+ mParentLocale, false, mIsCountryMode);
+ mLocaleOptions.clear();
+ }
+
private List getSortedLocaleList(
List localeInfos) {
final Locale sortingLocale = Locale.getDefault();
diff --git a/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java b/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java
index 83c87b03499..293c1ee0a6a 100644
--- a/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java
+++ b/src/com/android/settings/localepicker/RegionAndNumberingSystemPickerFragment.java
@@ -19,33 +19,39 @@ package com.android.settings.localepicker;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
import androidx.preference.PreferenceCategory;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.app.LocaleHelper;
import com.android.internal.app.LocaleStore;
-import com.android.internal.app.SystemLocaleCollector;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.widget.TopIntroPreference;
import com.google.android.material.appbar.AppBarLayout;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* A locale picker fragment to show region country and numbering system.
@@ -54,7 +60,8 @@ import java.util.Set;
* Allows the user to search for locales using both their native name and their name in the
* default locale.
*/
-public class RegionAndNumberingSystemPickerFragment extends DashboardFragment {
+public class RegionAndNumberingSystemPickerFragment extends DashboardFragment implements
+ SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
public static final String EXTRA_TARGET_LOCALE = "extra_target_locale";
public static final String EXTRA_IS_NUMBERING_SYSTEM = "extra_is_numbering_system";
@@ -63,17 +70,30 @@ public class RegionAndNumberingSystemPickerFragment extends DashboardFragment {
private static final String KEY_PREFERENCE_SYSTEM_LOCALE_LIST = "system_locale_list";
private static final String KEY_PREFERENCE_SYSTEM_LOCALE_SUGGESTED_LIST =
"system_locale_suggested_list";
+ private static final String KEY_TOP_INTRO_PREFERENCE = "top_intro_region";
+ private static final String EXTRA_EXPAND_SEARCH_VIEW = "expand_search_view";
@Nullable
- private SystemLocaleAllListPreferenceController mSystemLocaleAllListPreferenceController;
+ private SearchView mSearchView = null;
@Nullable
+ private SearchFilter mSearchFilter = null;
+ @SuppressWarnings("NullAway")
+ private SystemLocaleAllListPreferenceController mSystemLocaleAllListPreferenceController;
+ @SuppressWarnings("NullAway")
private SystemLocaleSuggestedListPreferenceController mSuggestedListPreferenceController;
@Nullable
private LocaleStore.LocaleInfo mLocaleInfo;
- private RecyclerView mRecyclerView;
+ @Nullable
+ private List mLocaleOptions;
+ @SuppressWarnings("NullAway")
+ private List mOriginalLocaleInfos;
private AppBarLayout mAppBarLayout;
+ private RecyclerView mRecyclerView;
private Activity mActivity;
+ private boolean mExpandSearch;
private boolean mIsNumberingMode;
+ @Nullable
+ private CharSequence mPrefix;
@Override
public void onCreate(@NonNull Bundle icicle) {
@@ -83,13 +103,27 @@ public class RegionAndNumberingSystemPickerFragment extends DashboardFragment {
Log.d(TAG, "onCreate, no activity or activity is finishing");
return;
}
+ setHasOptionsMenu(true);
- if (mLocaleInfo == null) {
- Log.d(TAG, "onCreate, can not get localeInfo");
- return;
+ mExpandSearch = mActivity.getIntent().getBooleanExtra(EXTRA_EXPAND_SEARCH_VIEW, false);
+ if (icicle != null) {
+ mExpandSearch = icicle.getBoolean(EXTRA_EXPAND_SEARCH_VIEW);
}
- mActivity.setTitle(mLocaleInfo.getFullNameNative());
+ Log.d(TAG, "onCreate, mIsNumberingMode = " + mIsNumberingMode);
+ if (!mIsNumberingMode) {
+ mActivity.setTitle(R.string.region_selection_title);
+ }
+
+ TopIntroPreference topIntroPreference = findPreference(KEY_TOP_INTRO_PREFERENCE);
+ if (topIntroPreference != null) {
+ topIntroPreference.setVisible(!mIsNumberingMode);
+ }
+
+ if (mSystemLocaleAllListPreferenceController != null) {
+ mOriginalLocaleInfos =
+ mSystemLocaleAllListPreferenceController.getSupportedLocaleList();
+ }
}
@Override
@@ -106,6 +140,151 @@ public class RegionAndNumberingSystemPickerFragment extends DashboardFragment {
mRecyclerView = view.findViewById(R.id.recycler_view);
}
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mSearchView != null) {
+ outState.putBoolean(EXTRA_EXPAND_SEARCH_VIEW, !mSearchView.isIconified());
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.language_selection_list, menu);
+ final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
+ if (searchMenuItem != null) {
+ searchMenuItem.setOnActionExpandListener(this);
+ mSearchView = (SearchView) searchMenuItem.getActionView();
+ mSearchView.setQueryHint(
+ getContext().getResources().getText(R.string.search_region_hint));
+ mSearchView.setOnQueryTextListener(this);
+ mSearchView.setMaxWidth(Integer.MAX_VALUE);
+ if (mExpandSearch) {
+ searchMenuItem.expandActionView();
+ }
+ }
+ }
+
+ private void filterSearch(@Nullable String query) {
+ if (mSearchFilter == null) {
+ mSearchFilter = new SearchFilter();
+ }
+
+ // If we haven't load apps list completely, don't filter anything.
+ if (mOriginalLocaleInfos == null) {
+ Log.w(TAG, "Locales haven't loaded completely yet, so nothing can be filtered");
+ return;
+ }
+ mSearchFilter.filter(query);
+ }
+
+ private class SearchFilter extends Filter {
+
+ @Override
+ protected FilterResults performFiltering(CharSequence prefix) {
+ FilterResults results = new FilterResults();
+ mPrefix = prefix;
+ if (TextUtils.isEmpty(prefix)) {
+ results.values = mOriginalLocaleInfos;
+ results.count = mOriginalLocaleInfos.size();
+ } else {
+ // TODO: decide if we should use the string's locale
+ List newList = new ArrayList<>(mOriginalLocaleInfos);
+ newList.addAll(mSystemLocaleAllListPreferenceController.getSuggestedLocaleList());
+ Locale locale = Locale.getDefault();
+ String prefixString = LocaleHelper.normalizeForSearch(prefix.toString(), locale);
+ final int count = newList.size();
+ final ArrayList newValues = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ final LocaleStore.LocaleInfo value = newList.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.contains(
+ value)) {
+ newValues.add(value);
+ }
+ }
+
+ results.values = newValues;
+ results.count = newValues.size();
+ }
+
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ if (mSystemLocaleAllListPreferenceController == null
+ || mSuggestedListPreferenceController == null) {
+ Log.d(TAG, "publishResults(), can not get preference.");
+ return;
+ }
+
+ mLocaleOptions = (ArrayList) results.values;
+ // TODO: Need to scroll to first preference when searching.
+ if (mRecyclerView != null) {
+ mRecyclerView.post(() -> mRecyclerView.scrollToPosition(0));
+ }
+
+ mSystemLocaleAllListPreferenceController.onSearchListChanged(mLocaleOptions, mPrefix);
+ mSuggestedListPreferenceController.onSearchListChanged(mLocaleOptions, mPrefix);
+ }
+
+ // TODO: decide if this is enough, or we want to use a BreakIterator...
+ private boolean wordMatches(String valueText, String prefixString) {
+ if (valueText == null) {
+ return false;
+ }
+
+ // First match against the whole, non-split value
+ if (valueText.startsWith(prefixString)) {
+ return true;
+ }
+
+ // For example: English (Australia), Arabic (Egypt)
+ Pattern pattern = Pattern.compile("^.*?\\((.*)");
+ Matcher matcher = pattern.matcher(valueText);
+ if (matcher.find()) {
+ String region = matcher.group(1);
+ return region.startsWith(prefixString);
+ }
+
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
+ // To prevent a large space on tool bar.
+ mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
+ // To prevent user can expand the collapsing tool bar view.
+ ViewCompat.setNestedScrollingEnabled(mRecyclerView, false);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
+ // We keep the collapsed status after user cancel the search function.
+ mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
+ ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(@Nullable String query) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(@Nullable String newText) {
+ filterSearch(newText);
+ return false;
+ }
+
@Override
protected String getLogTag() {
return TAG;
diff --git a/src/com/android/settings/localepicker/SystemLocalePickerFragment.java b/src/com/android/settings/localepicker/SystemLocalePickerFragment.java
index 5a007318a55..183990bfb75 100644
--- a/src/com/android/settings/localepicker/SystemLocalePickerFragment.java
+++ b/src/com/android/settings/localepicker/SystemLocalePickerFragment.java
@@ -222,8 +222,9 @@ public class SystemLocalePickerFragment extends DashboardFragment implements
if (mRecyclerView != null) {
mRecyclerView.post(() -> mRecyclerView.scrollToPosition(0));
}
- mSystemLocaleAllListPreferenceController.onSearchListChanged(mLocaleOptions);
- mSuggestedListPreferenceController.onSearchListChanged(mLocaleOptions);
+
+ mSystemLocaleAllListPreferenceController.onSearchListChanged(mLocaleOptions, null);
+ mSuggestedListPreferenceController.onSearchListChanged(mLocaleOptions, null);
}
// TODO: decide if this is enough, or we want to use a BreakIterator...