From 0f76903817c5b92f2417e08e06651429b52b7b43 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Fri, 20 Dec 2024 07:27:29 +0000 Subject: [PATCH] [Settings] Add search icon in region picker Bug: 38526548 Flag: EXEMPT refactor Change-Id: I1016f5f812d8f0c43a2b3a899bd70b6672f44481 --- res/values/strings.xml | 13 +- res/xml/system_language_picker.xml | 6 + .../LocaleListSearchCallback.java | 4 +- ...alePickerBaseListPreferenceController.java | 58 +++-- ...egionAndNumberingSystemPickerFragment.java | 201 +++++++++++++++++- .../SystemLocalePickerFragment.java | 5 +- 6 files changed, 257 insertions(+), 30 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 54b6fbc0da2..fe05a9861c8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -424,6 +424,9 @@ All languages + + More languages + All regions @@ -480,12 +483,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...