From 766c7951fbe8f29269acd908e5f053a93edae538 Mon Sep 17 00:00:00 2001 From: Geoffrey Boullanger Date: Tue, 3 Sep 2024 15:43:51 +0000 Subject: [PATCH] Update the time zone picker to work with location containing diacritics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, looking for a location containing diacritics (e.g. accents) requires the user to type in exactly those characters. With this change, diacritics are ignored and the strings are returned if they match (using startsWith). For example, looking for "reun" will show you "Réunion". Bug: b/364245352 Test: atest tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java Change-Id: I507a9ebc1c830ad3162fb2382814935fc337328d Flag: EXEMPT bugfix --- .../timezone/BaseTimeZoneAdapter.java | 23 ++++++++++-- .../datetime/timezone/RegionZonePicker.java | 2 +- .../timezone/BaseTimeZoneAdapterTest.java | 35 +++++++++++-------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java index 66735c8a5e1..e3ece219abf 100644 --- a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java +++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java @@ -33,9 +33,11 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; import com.android.settings.datetime.timezone.BaseTimeZonePicker.OnListItemClickListener; +import java.text.Normalizer; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.regex.Pattern; /** * Used with {@class BaseTimeZonePicker}. It renders text in each item into list view. A list of @@ -48,6 +50,9 @@ public class BaseTimeZoneAdapter @VisibleForTesting static final int TYPE_ITEM = 1; + private static final Pattern PATTERN_REMOVE_DIACRITICS = Pattern.compile( + "\\p{InCombiningDiacriticalMarks}+"); + private final List mOriginalItems; private final OnListItemClickListener mOnListItemClickListener; private final Locale mLocale; @@ -183,6 +188,19 @@ public class BaseTimeZoneAdapter } } + /** + * Removes diacritics (e.g. accents) from a string + */ + private static String removeDiacritics(final String str) { + if (str == null || str.isEmpty()) { + return str; + } + // decomposes the original characters into a base character and a diacritic sign + final String decomposed = Normalizer.normalize(str, Normalizer.Form.NFKD); + // replaces the diacritic signs with empty strings + return PATTERN_REMOVE_DIACRITICS.matcher(decomposed).replaceAll(""); + } + @VisibleForTesting public static class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { @@ -241,13 +259,14 @@ public class BaseTimeZoneAdapter if (TextUtils.isEmpty(prefix)) { newItems = mOriginalItems; } else { - final String prefixString = prefix.toString().toLowerCase(mLocale); + final String prefixString = removeDiacritics( + prefix.toString().toLowerCase(mLocale)); newItems = new ArrayList<>(); for (T item : mOriginalItems) { outer: for (String searchKey : item.getSearchKeys()) { - searchKey = searchKey.toLowerCase(mLocale); + searchKey = removeDiacritics(searchKey.toLowerCase(mLocale)); // First match against the whole, non-splitted value if (searchKey.startsWith(prefixString)) { newItems.add(item); diff --git a/src/com/android/settings/datetime/timezone/RegionZonePicker.java b/src/com/android/settings/datetime/timezone/RegionZonePicker.java index 1bc68a10d4c..06c0db59ac4 100644 --- a/src/com/android/settings/datetime/timezone/RegionZonePicker.java +++ b/src/com/android/settings/datetime/timezone/RegionZonePicker.java @@ -111,7 +111,7 @@ public class RegionZonePicker extends BaseTimeZoneInfoPicker { /** * Returns a list of {@link TimeZoneInfo} objects. The returned list will be sorted properly for - * display in the locale.It may be smaller than the input collection, if equivalent IDs are + * display in the locale. It may be smaller than the input collection, if equivalent IDs are * passed in. * * @param timeZoneIds a list of Olson IDs. diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java index 4f23b60c38d..77fafdecec6 100644 --- a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java +++ b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java @@ -22,39 +22,43 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class BaseTimeZoneAdapterTest { @Test public void testFilter() throws InterruptedException { - TestItem US = new TestItem("United States"); - TestItem HK = new TestItem("Hong Kong"); - TestItem UK = new TestItem("United Kingdom", new String[] { "United Kingdom", + TestItem unitedStates = new TestItem("United States"); + TestItem hongKong = new TestItem("Hong Kong"); + TestItem unitedKingdom = new TestItem("United Kingdom", new String[]{"United Kingdom", "Great Britain"}); - TestItem secretCountry = new TestItem("no name", new String[] { "Secret"}); + TestItem reunion = new TestItem("Réunion"); + TestItem secretCountry = new TestItem("no name", new String[]{"Secret"}); List items = new ArrayList<>(); - items.add(US); - items.add(HK); - items.add(UK); + items.add(unitedStates); + items.add(hongKong); + items.add(unitedKingdom); + items.add(reunion); items.add(secretCountry); TestTimeZoneAdapter adapter = new TestTimeZoneAdapter(items); assertSearch(adapter, "", items.toArray(new TestItem[0])); - assertSearch(adapter, "Unit", US, UK); - assertSearch(adapter, "kon", HK); - assertSearch(adapter, "brit", UK); + assertSearch(adapter, "Unit", unitedStates, unitedKingdom); + assertSearch(adapter, "kon", hongKong); + assertSearch(adapter, "brit", unitedKingdom); assertSearch(adapter, "sec", secretCountry); + assertSearch(adapter, "reun", reunion); // no accent in search, accent in result + assertSearch(adapter, "Réunion", reunion); // accents in search and result } - private void assertSearch(TestTimeZoneAdapter adapter , String searchText, TestItem... items) + private void assertSearch(TestTimeZoneAdapter adapter, String searchText, TestItem... items) throws InterruptedException { Observer observer = new Observer(adapter); adapter.getFilter().filter(searchText); @@ -89,7 +93,10 @@ public class BaseTimeZoneAdapterTest { private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter { private TestTimeZoneAdapter(List items) { - super(items, position -> {}, Locale.US, false /* showItemSummary */, + super(items, + position -> {}, + Locale.US, + false /* showItemSummary */, null /* headerText */); } } @@ -100,7 +107,7 @@ public class BaseTimeZoneAdapterTest { private final String[] mSearchKeys; TestItem(String title) { - this(title, new String[] { title }); + this(title, new String[]{title}); } TestItem(String title, String[] searchKeys) {