Update the time zone picker to work with location containing diacritics
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
This commit is contained in:
@@ -33,9 +33,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.datetime.timezone.BaseTimeZonePicker.OnListItemClickListener;
|
import com.android.settings.datetime.timezone.BaseTimeZonePicker.OnListItemClickListener;
|
||||||
|
|
||||||
|
import java.text.Normalizer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
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
|
* Used with {@class BaseTimeZonePicker}. It renders text in each item into list view. A list of
|
||||||
@@ -48,6 +50,9 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int TYPE_ITEM = 1;
|
static final int TYPE_ITEM = 1;
|
||||||
|
|
||||||
|
private static final Pattern PATTERN_REMOVE_DIACRITICS = Pattern.compile(
|
||||||
|
"\\p{InCombiningDiacriticalMarks}+");
|
||||||
|
|
||||||
private final List<T> mOriginalItems;
|
private final List<T> mOriginalItems;
|
||||||
private final OnListItemClickListener<T> mOnListItemClickListener;
|
private final OnListItemClickListener<T> mOnListItemClickListener;
|
||||||
private final Locale mLocale;
|
private final Locale mLocale;
|
||||||
@@ -183,6 +188,19 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@VisibleForTesting
|
||||||
public static class ItemViewHolder<T extends BaseTimeZoneAdapter.AdapterItem>
|
public static class ItemViewHolder<T extends BaseTimeZoneAdapter.AdapterItem>
|
||||||
extends RecyclerView.ViewHolder implements View.OnClickListener {
|
extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
@@ -241,13 +259,14 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
|||||||
if (TextUtils.isEmpty(prefix)) {
|
if (TextUtils.isEmpty(prefix)) {
|
||||||
newItems = mOriginalItems;
|
newItems = mOriginalItems;
|
||||||
} else {
|
} else {
|
||||||
final String prefixString = prefix.toString().toLowerCase(mLocale);
|
final String prefixString = removeDiacritics(
|
||||||
|
prefix.toString().toLowerCase(mLocale));
|
||||||
newItems = new ArrayList<>();
|
newItems = new ArrayList<>();
|
||||||
|
|
||||||
for (T item : mOriginalItems) {
|
for (T item : mOriginalItems) {
|
||||||
outer:
|
outer:
|
||||||
for (String searchKey : item.getSearchKeys()) {
|
for (String searchKey : item.getSearchKeys()) {
|
||||||
searchKey = searchKey.toLowerCase(mLocale);
|
searchKey = removeDiacritics(searchKey.toLowerCase(mLocale));
|
||||||
// First match against the whole, non-splitted value
|
// First match against the whole, non-splitted value
|
||||||
if (searchKey.startsWith(prefixString)) {
|
if (searchKey.startsWith(prefixString)) {
|
||||||
newItems.add(item);
|
newItems.add(item);
|
||||||
|
@@ -22,36 +22,40 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class BaseTimeZoneAdapterTest {
|
public class BaseTimeZoneAdapterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilter() throws InterruptedException {
|
public void testFilter() throws InterruptedException {
|
||||||
TestItem US = new TestItem("United States");
|
TestItem unitedStates = new TestItem("United States");
|
||||||
TestItem HK = new TestItem("Hong Kong");
|
TestItem hongKong = new TestItem("Hong Kong");
|
||||||
TestItem UK = new TestItem("United Kingdom", new String[] { "United Kingdom",
|
TestItem unitedKingdom = new TestItem("United Kingdom", new String[]{"United Kingdom",
|
||||||
"Great Britain"});
|
"Great Britain"});
|
||||||
|
TestItem reunion = new TestItem("Réunion");
|
||||||
TestItem secretCountry = new TestItem("no name", new String[]{"Secret"});
|
TestItem secretCountry = new TestItem("no name", new String[]{"Secret"});
|
||||||
List<TestItem> items = new ArrayList<>();
|
List<TestItem> items = new ArrayList<>();
|
||||||
items.add(US);
|
items.add(unitedStates);
|
||||||
items.add(HK);
|
items.add(hongKong);
|
||||||
items.add(UK);
|
items.add(unitedKingdom);
|
||||||
|
items.add(reunion);
|
||||||
items.add(secretCountry);
|
items.add(secretCountry);
|
||||||
|
|
||||||
TestTimeZoneAdapter adapter = new TestTimeZoneAdapter(items);
|
TestTimeZoneAdapter adapter = new TestTimeZoneAdapter(items);
|
||||||
assertSearch(adapter, "", items.toArray(new TestItem[0]));
|
assertSearch(adapter, "", items.toArray(new TestItem[0]));
|
||||||
assertSearch(adapter, "Unit", US, UK);
|
assertSearch(adapter, "Unit", unitedStates, unitedKingdom);
|
||||||
assertSearch(adapter, "kon", HK);
|
assertSearch(adapter, "kon", hongKong);
|
||||||
assertSearch(adapter, "brit", UK);
|
assertSearch(adapter, "brit", unitedKingdom);
|
||||||
assertSearch(adapter, "sec", secretCountry);
|
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)
|
||||||
@@ -89,7 +93,10 @@ public class BaseTimeZoneAdapterTest {
|
|||||||
private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter<TestItem> {
|
private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter<TestItem> {
|
||||||
|
|
||||||
private TestTimeZoneAdapter(List<TestItem> items) {
|
private TestTimeZoneAdapter(List<TestItem> items) {
|
||||||
super(items, position -> {}, Locale.US, false /* showItemSummary */,
|
super(items,
|
||||||
|
position -> {},
|
||||||
|
Locale.US,
|
||||||
|
false /* showItemSummary */,
|
||||||
null /* headerText */);
|
null /* headerText */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user