From a15948b53eb8a019e3c07680f8ea3bdb45acdb88 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Tue, 12 Nov 2024 13:04:23 +0000 Subject: [PATCH] [Settings] Refactor: Add SystemLocalePickerFragment Bug: 377664066 Flag: EXEMPT refactor Change-Id: I79805c639197911d10f3632e50b5feced08c2fd6 --- res/menu/language_selection_list.xml | 25 ++ res/values/strings.xml | 21 ++ res/xml/system_language_picker.xml | 30 ++ .../SystemLocalePickerFragment.java | 281 ++++++++++++++++++ 4 files changed, 357 insertions(+) create mode 100644 res/menu/language_selection_list.xml create mode 100644 res/xml/system_language_picker.xml create mode 100644 src/com/android/settings/localepicker/SystemLocalePickerFragment.java diff --git a/res/menu/language_selection_list.xml b/res/menu/language_selection_list.xml new file mode 100644 index 00000000000..799d3c97c4a --- /dev/null +++ b/res/menu/language_selection_list.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 8b5e63f4329..54b7f67b338 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -402,6 +402,18 @@ All languages + + Suggested + + + All languages + + + All regions + + + All numbering systems + System language @@ -450,6 +462,15 @@ This language can’t be used as a system language, but you’ve let apps and websites know you prefer this language. + + Search + + Add a language + + Region preference + + Type language name + Regional preferences diff --git a/res/xml/system_language_picker.xml b/res/xml/system_language_picker.xml new file mode 100644 index 00000000000..cccf56ea063 --- /dev/null +++ b/res/xml/system_language_picker.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/src/com/android/settings/localepicker/SystemLocalePickerFragment.java b/src/com/android/settings/localepicker/SystemLocalePickerFragment.java new file mode 100644 index 00000000000..df3ae8454f4 --- /dev/null +++ b/src/com/android/settings/localepicker/SystemLocalePickerFragment.java @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2024 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.settings.localepicker; + +import android.app.Activity; +import android.app.settings.SettingsEnums; +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.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import com.google.android.material.appbar.AppBarLayout; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * A locale picker fragment to show system languages. + * + *

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.

+ */ +public class SystemLocalePickerFragment extends DashboardFragment implements + SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener { + + private static final String TAG = "SystemLocalePickerFragment"; + private static final String EXTRA_EXPAND_SEARCH_VIEW = "expand_search_view"; + + @Nullable private SearchView mSearchView = null; + @Nullable private SearchFilter mSearchFilter = null; + @Nullable private Set mLocaleList; + @Nullable private List mLocaleOptions; + @Nullable private List mOriginalLocaleInfos; + private AppBarLayout mAppBarLayout; + private RecyclerView mRecyclerView; + private Activity mActivity; + private boolean mExpandSearch; + + @Override + public void onCreate(@NonNull Bundle icicle) { + super.onCreate(icicle); + mActivity = getActivity(); + if (mActivity.isFinishing()) { + return; + } + setHasOptionsMenu(true); + + mExpandSearch = mActivity.getIntent().getBooleanExtra(EXTRA_EXPAND_SEARCH_VIEW, false); + if (icicle != null) { + mExpandSearch = icicle.getBoolean(EXTRA_EXPAND_SEARCH_VIEW); + } + + SystemLocaleCollector systemLocaleCollector = new SystemLocaleCollector(getContext(), null); + mLocaleList = systemLocaleCollector.getSupportedLocaleList(null, false, false); + mLocaleOptions = new ArrayList<>(mLocaleList.size()); + } + + @Override + public @NonNull View onCreateView(@NonNull LayoutInflater inflater, + @NonNull ViewGroup container, @NonNull Bundle savedInstanceState) { + mAppBarLayout = mActivity.findViewById(R.id.app_bar); + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + 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_language_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(); + } + + // TODO: b/30358431 - Add preference of system locales. + // mOriginalLocaleInfos = mSystemLocaleAllListPreferenceController.getSupportedLocaleList(); + // 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(); + + if (mOriginalLocaleInfos == null) { + mOriginalLocaleInfos = new ArrayList<>(mLocaleList); + } + + if (TextUtils.isEmpty(prefix)) { + results.values = mOriginalLocaleInfos; + results.count = mOriginalLocaleInfos.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 = mOriginalLocaleInfos.size(); + final ArrayList newValues = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + final LocaleStore.LocaleInfo value = mOriginalLocaleInfos.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) { + mLocaleOptions = (ArrayList) results.values; + // Need to scroll to first preference when searching. + if (mRecyclerView != null) { + mRecyclerView.post(() -> mRecyclerView.scrollToPosition(0)); + } + // TODO: b/30358431 - Add preference of system locales. + // mSystemLocaleAllListPreferenceController.onSearchListChanged(mLocaleOptions); + // mSuggestedListPreferenceController.onSearchListChanged(mLocaleOptions); + } + + // 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; + } + + return Arrays.stream(valueText.split(" ")) + .anyMatch(word -> word.startsWith(prefixString)); + } + } + + @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; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.USER_LOCALE_LIST; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.system_language_picker; + } + + @Override + protected List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getSettingsLifecycle()); + } + + private List buildPreferenceControllers( + @NonNull Context context, @Nullable Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + // TODO: b/30358431 - Add preference of system locales. + return controllers; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.system_language_picker); +}