From ab146531c0710ad46ac347d280b14c798f732a12 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 19 Dec 2017 11:28:51 -0800 Subject: Support dual alphabets in smart dial. Bug: 30215380,70633239 Test: CompositeSmartDialMapTest, LatinSmartDialMapTest, RussianSmartDialMapTest, SmartDialNameMatcherTest PiperOrigin-RevId: 179580982 Change-Id: I5e4c3e61f0dfdc6ca1e80a93bb985ffec08dd8b0 --- java/com/android/dialer/app/DialtactsActivity.java | 2 +- .../app/list/SmartDialNumberListAdapter.java | 9 +- .../dialer/database/DialerDatabaseHelper.java | 9 +- .../dialer/dialpadview/SmartDialCursorLoader.java | 5 +- .../dialer/smartdial/CompositeSmartDialMap.java | 170 +++++ .../dialer/smartdial/LatinSmartDialMap.java | 794 ++++++++++----------- .../dialer/smartdial/RussianSmartDialMap.java | 94 +++ .../com/android/dialer/smartdial/SmartDialMap.java | 97 ++- .../dialer/smartdial/SmartDialNameMatcher.java | 73 +- .../android/dialer/smartdial/SmartDialPrefix.java | 33 +- 10 files changed, 800 insertions(+), 486 deletions(-) create mode 100644 java/com/android/dialer/smartdial/CompositeSmartDialMap.java create mode 100644 java/com/android/dialer/smartdial/RussianSmartDialMap.java (limited to 'java/com/android/dialer') diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 1a549abf3..eb95a4ee4 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -1400,7 +1400,7 @@ public class DialtactsActivity extends TransactionSafeActivity mNewSearchFragment.setRawNumber(query); } final String normalizedQuery = - SmartDialNameMatcher.normalizeNumber(query, SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); + SmartDialNameMatcher.normalizeNumber(/* context = */ this, query); if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { if (DEBUG) { diff --git a/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java b/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java index 3b00c7643..5b48ccfd0 100644 --- a/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java +++ b/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java @@ -25,7 +25,6 @@ import com.android.dialer.common.LogUtil; import com.android.dialer.dialpadview.SmartDialCursorLoader; import com.android.dialer.smartdial.SmartDialMatchPosition; import com.android.dialer.smartdial.SmartDialNameMatcher; -import com.android.dialer.smartdial.SmartDialPrefix; import com.android.dialer.util.CallUtil; import java.util.ArrayList; @@ -35,11 +34,13 @@ public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter { private static final String TAG = SmartDialNumberListAdapter.class.getSimpleName(); private static final boolean DEBUG = false; + private final Context mContext; @NonNull private final SmartDialNameMatcher mNameMatcher; public SmartDialNumberListAdapter(Context context) { super(context); - mNameMatcher = new SmartDialNameMatcher("", SmartDialPrefix.getMap()); + mContext = context; + mNameMatcher = new SmartDialNameMatcher(""); setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_DIRECT_CALL, false); if (DEBUG) { @@ -72,7 +73,7 @@ public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter { protected void setHighlight(ContactListItemView view, Cursor cursor) { view.clearHighlightSequences(); - if (mNameMatcher.matches(cursor.getString(PhoneQuery.DISPLAY_NAME))) { + if (mNameMatcher.matches(mContext, cursor.getString(PhoneQuery.DISPLAY_NAME))) { final ArrayList nameMatches = mNameMatcher.getMatchPositions(); for (SmartDialMatchPosition match : nameMatches) { view.addNameHighlightSequence(match.start, match.end); @@ -89,7 +90,7 @@ public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter { } final SmartDialMatchPosition numberMatch = - mNameMatcher.matchesNumber(cursor.getString(PhoneQuery.PHONE_NUMBER)); + mNameMatcher.matchesNumber(mContext, cursor.getString(PhoneQuery.PHONE_NUMBER)); if (numberMatch != null) { view.addNumberHighlightSequence(numberMatch.start, numberMatch.end); } diff --git a/java/com/android/dialer/database/DialerDatabaseHelper.java b/java/com/android/dialer/database/DialerDatabaseHelper.java index 113e86314..b0bd62a34 100644 --- a/java/com/android/dialer/database/DialerDatabaseHelper.java +++ b/java/com/android/dialer/database/DialerDatabaseHelper.java @@ -535,7 +535,7 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { insert.executeInsert(); final String contactPhoneNumber = updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER); final ArrayList numberPrefixes = - SmartDialPrefix.parseToNumberTokens(contactPhoneNumber); + SmartDialPrefix.parseToNumberTokens(mContext, contactPhoneNumber); for (String numberPrefix : numberPrefixes) { numberInsert.bindLong(1, updatedContactCursor.getLong(PhoneQuery.PHONE_CONTACT_ID)); @@ -578,7 +578,7 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { while (nameCursor.moveToNext()) { /** Computes a list of prefixes of a given contact name. */ final ArrayList namePrefixes = - SmartDialPrefix.generateNamePrefixes(nameCursor.getString(columnIndexName)); + SmartDialPrefix.generateNamePrefixes(mContext, nameCursor.getString(columnIndexName)); for (String namePrefix : namePrefixes) { insert.bindLong(1, nameCursor.getLong(columnIndexContactId)); @@ -912,8 +912,9 @@ public class DialerDatabaseHelper extends SQLiteOpenHelper { /** * If the contact has either the name or number that matches the query, add to the result. */ - final boolean nameMatches = nameMatcher.matches(displayName); - final boolean numberMatches = (nameMatcher.matchesNumber(phoneNumber, query) != null); + final boolean nameMatches = nameMatcher.matches(mContext, displayName); + final boolean numberMatches = + (nameMatcher.matchesNumber(mContext, phoneNumber, query) != null); if (nameMatches || numberMatches) { /** If a contact has not been added, add it to the result and the hash set. */ duplicates.add(contactMatch); diff --git a/java/com/android/dialer/dialpadview/SmartDialCursorLoader.java b/java/com/android/dialer/dialpadview/SmartDialCursorLoader.java index 271535fce..d085b55bd 100644 --- a/java/com/android/dialer/dialpadview/SmartDialCursorLoader.java +++ b/java/com/android/dialer/dialpadview/SmartDialCursorLoader.java @@ -26,7 +26,6 @@ import com.android.dialer.database.Database; import com.android.dialer.database.DialerDatabaseHelper; import com.android.dialer.database.DialerDatabaseHelper.ContactNumber; import com.android.dialer.smartdial.SmartDialNameMatcher; -import com.android.dialer.smartdial.SmartDialPrefix; import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; @@ -59,10 +58,10 @@ public class SmartDialCursorLoader extends AsyncTaskLoader { if (DEBUG) { LogUtil.v(TAG, "Configure new query to be " + query); } - mQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialPrefix.getMap()); + mQuery = SmartDialNameMatcher.normalizeNumber(mContext, query); /** Constructs a name matcher object for matching names. */ - mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap()); + mNameMatcher = new SmartDialNameMatcher(mQuery); mNameMatcher.setShouldMatchEmptyQuery(!mShowEmptyListForNullQuery); } diff --git a/java/com/android/dialer/smartdial/CompositeSmartDialMap.java b/java/com/android/dialer/smartdial/CompositeSmartDialMap.java new file mode 100644 index 000000000..d51e46f76 --- /dev/null +++ b/java/com/android/dialer/smartdial/CompositeSmartDialMap.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 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.dialer.smartdial; + +import android.content.Context; +import android.support.annotation.VisibleForTesting; +import android.support.v4.util.SimpleArrayMap; +import com.android.dialer.compat.CompatUtils; +import com.android.dialer.configprovider.ConfigProviderBindings; +import com.google.common.base.Optional; + +/** + * A utility class that combines the functionality of two implementations of {@link SmartDialMap} so + * that we support smart dial for dual alphabets. + * + *

Of the two implementations of {@link SmartDialMap}, the default one always takes precedence. + * The second one is consulted only when the default one is unable to provide a valid result. + * + *

Note that the second implementation can be absent if it is not defined for the system's 1st + * language preference. + */ +@SuppressWarnings("Guava") +public class CompositeSmartDialMap { + @VisibleForTesting + public static final String FLAG_ENABLE_DUAL_ALPHABETS = "enable_dual_alphabets_on_t9"; + + private static final SmartDialMap DEFAULT_MAP = LatinSmartDialMap.getInstance(); + + // A map in which each key is an ISO 639-2 language code and the corresponding value is a + // SmartDialMap + private static final SimpleArrayMap EXTRA_MAPS = new SimpleArrayMap<>(); + + static { + EXTRA_MAPS.put("rus", RussianSmartDialMap.getInstance()); + } + + private CompositeSmartDialMap() {} + + /** + * Returns true if the provided character can be mapped to a key on the dialpad. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. + */ + static boolean isValidDialpadCharacter(Context context, char ch) { + if (DEFAULT_MAP.isValidDialpadCharacter(ch)) { + return true; + } + + Optional extraMap = getExtraMap(context); + return extraMap.isPresent() && extraMap.get().isValidDialpadCharacter(ch); + } + + /** + * Returns true if the provided character is a letter, and can be mapped to a key on the dialpad. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. + */ + static boolean isValidDialpadAlphabeticChar(Context context, char ch) { + if (DEFAULT_MAP.isValidDialpadAlphabeticChar(ch)) { + return true; + } + + Optional extraMap = getExtraMap(context); + return extraMap.isPresent() && extraMap.get().isValidDialpadAlphabeticChar(ch); + } + + /** + * Returns true if the provided character is a digit, and can be mapped to a key on the dialpad. + */ + static boolean isValidDialpadNumericChar(Context context, char ch) { + if (DEFAULT_MAP.isValidDialpadNumericChar(ch)) { + return true; + } + + Optional extraMap = getExtraMap(context); + return extraMap.isPresent() && extraMap.get().isValidDialpadNumericChar(ch); + } + + /** + * Get the index of the key on the dialpad which the character corresponds to. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. + * + *

If the provided character can't be mapped to a key on the dialpad, return -1. + */ + static byte getDialpadIndex(Context context, char ch) { + Optional dialpadIndex = DEFAULT_MAP.getDialpadIndex(ch); + if (dialpadIndex.isPresent()) { + return dialpadIndex.get(); + } + + Optional extraMap = getExtraMap(context); + if (extraMap.isPresent()) { + dialpadIndex = extraMap.get().getDialpadIndex(ch); + } + + return dialpadIndex.isPresent() ? dialpadIndex.get() : -1; + } + + /** + * Get the actual numeric character on the dialpad which the character corresponds to. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. + * + *

If the provided character can't be mapped to a key on the dialpad, return the character. + */ + static char getDialpadNumericCharacter(Context context, char ch) { + Optional dialpadNumericChar = DEFAULT_MAP.getDialpadNumericCharacter(ch); + if (dialpadNumericChar.isPresent()) { + return dialpadNumericChar.get(); + } + + Optional extraMap = getExtraMap(context); + if (extraMap.isPresent()) { + dialpadNumericChar = extraMap.get().getDialpadNumericCharacter(ch); + } + + return dialpadNumericChar.isPresent() ? dialpadNumericChar.get() : ch; + } + + /** + * Converts uppercase characters to lower case ones, and on a best effort basis, strips accents + * from accented characters. + * + *

If the provided character can't be mapped to a key on the dialpad, return the character. + */ + static char normalizeCharacter(Context context, char ch) { + Optional normalizedChar = DEFAULT_MAP.normalizeCharacter(ch); + if (normalizedChar.isPresent()) { + return normalizedChar.get(); + } + + Optional extraMap = getExtraMap(context); + if (extraMap.isPresent()) { + normalizedChar = extraMap.get().normalizeCharacter(ch); + } + + return normalizedChar.isPresent() ? normalizedChar.get() : ch; + } + + @VisibleForTesting + static Optional getExtraMap(Context context) { + if (!ConfigProviderBindings.get(context).getBoolean(FLAG_ENABLE_DUAL_ALPHABETS, false)) { + return Optional.absent(); + } + + String languageCode = CompatUtils.getLocale(context).getISO3Language(); + return EXTRA_MAPS.containsKey(languageCode) + ? Optional.of(EXTRA_MAPS.get(languageCode)) + : Optional.absent(); + } +} diff --git a/java/com/android/dialer/smartdial/LatinSmartDialMap.java b/java/com/android/dialer/smartdial/LatinSmartDialMap.java index 656fd12b2..b67901bbe 100644 --- a/java/com/android/dialer/smartdial/LatinSmartDialMap.java +++ b/java/com/android/dialer/smartdial/LatinSmartDialMap.java @@ -16,53 +16,63 @@ package com.android.dialer.smartdial; -/** {@link SmartDialMap} for Latin based T9 dialpad searching. */ -public class LatinSmartDialMap implements SmartDialMap { +import android.support.v4.util.SimpleArrayMap; +import com.google.common.base.Optional; - private static final char[] LATIN_LETTERS_TO_DIGITS = { - '2', - '2', - '2', // A,B,C -> 2 - '3', - '3', - '3', // D,E,F -> 3 - '4', - '4', - '4', // G,H,I -> 4 - '5', - '5', - '5', // J,K,L -> 5 - '6', - '6', - '6', // M,N,O -> 6 - '7', - '7', - '7', - '7', // P,Q,R,S -> 7 - '8', - '8', - '8', // T,U,V -> 8 - '9', - '9', - '9', - '9' // W,X,Y,Z -> 9 - }; +/** A {@link SmartDialMap} for the Latin alphabet, which is for T9 dialpad searching. */ +@SuppressWarnings("Guava") +final class LatinSmartDialMap extends SmartDialMap { + private static final SimpleArrayMap CHAR_TO_KEY_MAP = + new SimpleArrayMap<>(); - @Override - public boolean isValidDialpadAlphabeticChar(char ch) { - return (ch >= 'a' && ch <= 'z'); - } + static { + CHAR_TO_KEY_MAP.put('a', '2'); + CHAR_TO_KEY_MAP.put('b', '2'); + CHAR_TO_KEY_MAP.put('c', '2'); - @Override - public boolean isValidDialpadNumericChar(char ch) { - return (ch >= '0' && ch <= '9'); + CHAR_TO_KEY_MAP.put('d', '3'); + CHAR_TO_KEY_MAP.put('e', '3'); + CHAR_TO_KEY_MAP.put('f', '3'); + + CHAR_TO_KEY_MAP.put('g', '4'); + CHAR_TO_KEY_MAP.put('h', '4'); + CHAR_TO_KEY_MAP.put('i', '4'); + + CHAR_TO_KEY_MAP.put('j', '5'); + CHAR_TO_KEY_MAP.put('k', '5'); + CHAR_TO_KEY_MAP.put('l', '5'); + + CHAR_TO_KEY_MAP.put('m', '6'); + CHAR_TO_KEY_MAP.put('n', '6'); + CHAR_TO_KEY_MAP.put('o', '6'); + + CHAR_TO_KEY_MAP.put('p', '7'); + CHAR_TO_KEY_MAP.put('q', '7'); + CHAR_TO_KEY_MAP.put('r', '7'); + CHAR_TO_KEY_MAP.put('s', '7'); + + CHAR_TO_KEY_MAP.put('t', '8'); + CHAR_TO_KEY_MAP.put('u', '8'); + CHAR_TO_KEY_MAP.put('v', '8'); + + CHAR_TO_KEY_MAP.put('w', '9'); + CHAR_TO_KEY_MAP.put('x', '9'); + CHAR_TO_KEY_MAP.put('y', '9'); + CHAR_TO_KEY_MAP.put('z', '9'); } - @Override - public boolean isValidDialpadCharacter(char ch) { - return (isValidDialpadAlphabeticChar(ch) || isValidDialpadNumericChar(ch)); + private static LatinSmartDialMap instance; + + static LatinSmartDialMap getInstance() { + if (instance == null) { + instance = new LatinSmartDialMap(); + } + + return instance; } + private LatinSmartDialMap() {} + /* * The switch statement in this function was generated using the python code: * from unidecode import unidecode @@ -72,7 +82,7 @@ public class LatinSmartDialMap implements SmartDialMap { * # Unicode characters that decompose into multiple characters i.e. * # into ss are not supported for now * if (len(decoded) == 1 and decoded.isalpha()): - * print "case '" + char + "': return '" + unidecode(char) + "';" + * print "case '" + char + "': return Optional.of('" + unidecode(char) + "');" * * This gives us a way to map characters containing accents/diacritics to their * alphabetic equivalents. The unidecode library can be found at: @@ -81,705 +91,695 @@ public class LatinSmartDialMap implements SmartDialMap { * Also remaps all upper case latin characters to their lower case equivalents. */ @Override - public char normalizeCharacter(char ch) { + Optional normalizeCharacter(char ch) { + if (isValidDialpadAlphabeticChar(ch)) { + return Optional.of(ch); + } + switch (ch) { case 'À': - return 'a'; + return Optional.of('a'); case 'Á': - return 'a'; + return Optional.of('a'); case 'Â': - return 'a'; + return Optional.of('a'); case 'Ã': - return 'a'; + return Optional.of('a'); case 'Ä': - return 'a'; + return Optional.of('a'); case 'Å': - return 'a'; + return Optional.of('a'); case 'Ç': - return 'c'; + return Optional.of('c'); case 'È': - return 'e'; + return Optional.of('e'); case 'É': - return 'e'; + return Optional.of('e'); case 'Ê': - return 'e'; + return Optional.of('e'); case 'Ë': - return 'e'; + return Optional.of('e'); case 'Ì': - return 'i'; + return Optional.of('i'); case 'Í': - return 'i'; + return Optional.of('i'); case 'Î': - return 'i'; + return Optional.of('i'); case 'Ï': - return 'i'; + return Optional.of('i'); case 'Ð': - return 'd'; + return Optional.of('d'); case 'Ñ': - return 'n'; + return Optional.of('n'); case 'Ò': - return 'o'; + return Optional.of('o'); case 'Ó': - return 'o'; + return Optional.of('o'); case 'Ô': - return 'o'; + return Optional.of('o'); case 'Õ': - return 'o'; + return Optional.of('o'); case 'Ö': - return 'o'; + return Optional.of('o'); case '×': - return 'x'; + return Optional.of('x'); case 'Ø': - return 'o'; + return Optional.of('o'); case 'Ù': - return 'u'; + return Optional.of('u'); case 'Ú': - return 'u'; + return Optional.of('u'); case 'Û': - return 'u'; + return Optional.of('u'); case 'Ü': - return 'u'; + return Optional.of('u'); case 'Ý': - return 'u'; + return Optional.of('u'); case 'à': - return 'a'; + return Optional.of('a'); case 'á': - return 'a'; + return Optional.of('a'); case 'â': - return 'a'; + return Optional.of('a'); case 'ã': - return 'a'; + return Optional.of('a'); case 'ä': - return 'a'; + return Optional.of('a'); case 'å': - return 'a'; + return Optional.of('a'); case 'ç': - return 'c'; + return Optional.of('c'); case 'è': - return 'e'; + return Optional.of('e'); case 'é': - return 'e'; + return Optional.of('e'); case 'ê': - return 'e'; + return Optional.of('e'); case 'ë': - return 'e'; + return Optional.of('e'); case 'ì': - return 'i'; + return Optional.of('i'); case 'í': - return 'i'; + return Optional.of('i'); case 'î': - return 'i'; + return Optional.of('i'); case 'ï': - return 'i'; + return Optional.of('i'); case 'ð': - return 'd'; + return Optional.of('d'); case 'ñ': - return 'n'; + return Optional.of('n'); case 'ò': - return 'o'; + return Optional.of('o'); case 'ó': - return 'o'; + return Optional.of('o'); case 'ô': - return 'o'; + return Optional.of('o'); case 'õ': - return 'o'; + return Optional.of('o'); case 'ö': - return 'o'; + return Optional.of('o'); case 'ø': - return 'o'; + return Optional.of('o'); case 'ù': - return 'u'; + return Optional.of('u'); case 'ú': - return 'u'; + return Optional.of('u'); case 'û': - return 'u'; + return Optional.of('u'); case 'ü': - return 'u'; + return Optional.of('u'); case 'ý': - return 'y'; + return Optional.of('y'); case 'ÿ': - return 'y'; + return Optional.of('y'); case 'Ā': - return 'a'; + return Optional.of('a'); case 'ā': - return 'a'; + return Optional.of('a'); case 'Ă': - return 'a'; + return Optional.of('a'); case 'ă': - return 'a'; + return Optional.of('a'); case 'Ą': - return 'a'; + return Optional.of('a'); case 'ą': - return 'a'; + return Optional.of('a'); case 'Ć': - return 'c'; + return Optional.of('c'); case 'ć': - return 'c'; + return Optional.of('c'); case 'Ĉ': - return 'c'; + return Optional.of('c'); case 'ĉ': - return 'c'; + return Optional.of('c'); case 'Ċ': - return 'c'; + return Optional.of('c'); case 'ċ': - return 'c'; + return Optional.of('c'); case 'Č': - return 'c'; + return Optional.of('c'); case 'č': - return 'c'; + return Optional.of('c'); case 'Ď': - return 'd'; + return Optional.of('d'); case 'ď': - return 'd'; + return Optional.of('d'); case 'Đ': - return 'd'; + return Optional.of('d'); case 'đ': - return 'd'; + return Optional.of('d'); case 'Ē': - return 'e'; + return Optional.of('e'); case 'ē': - return 'e'; + return Optional.of('e'); case 'Ĕ': - return 'e'; + return Optional.of('e'); case 'ĕ': - return 'e'; + return Optional.of('e'); case 'Ė': - return 'e'; + return Optional.of('e'); case 'ė': - return 'e'; + return Optional.of('e'); case 'Ę': - return 'e'; + return Optional.of('e'); case 'ę': - return 'e'; + return Optional.of('e'); case 'Ě': - return 'e'; + return Optional.of('e'); case 'ě': - return 'e'; + return Optional.of('e'); case 'Ĝ': - return 'g'; + return Optional.of('g'); case 'ĝ': - return 'g'; + return Optional.of('g'); case 'Ğ': - return 'g'; + return Optional.of('g'); case 'ğ': - return 'g'; + return Optional.of('g'); case 'Ġ': - return 'g'; + return Optional.of('g'); case 'ġ': - return 'g'; + return Optional.of('g'); case 'Ģ': - return 'g'; + return Optional.of('g'); case 'ģ': - return 'g'; + return Optional.of('g'); case 'Ĥ': - return 'h'; + return Optional.of('h'); case 'ĥ': - return 'h'; + return Optional.of('h'); case 'Ħ': - return 'h'; + return Optional.of('h'); case 'ħ': - return 'h'; + return Optional.of('h'); case 'Ĩ': - return 'i'; + return Optional.of('i'); case 'ĩ': - return 'i'; + return Optional.of('i'); case 'Ī': - return 'i'; + return Optional.of('i'); case 'ī': - return 'i'; + return Optional.of('i'); case 'Ĭ': - return 'i'; + return Optional.of('i'); case 'ĭ': - return 'i'; + return Optional.of('i'); case 'Į': - return 'i'; + return Optional.of('i'); case 'į': - return 'i'; + return Optional.of('i'); case 'İ': - return 'i'; + return Optional.of('i'); case 'ı': - return 'i'; + return Optional.of('i'); case 'Ĵ': - return 'j'; + return Optional.of('j'); case 'ĵ': - return 'j'; + return Optional.of('j'); case 'Ķ': - return 'k'; + return Optional.of('k'); case 'ķ': - return 'k'; + return Optional.of('k'); case 'ĸ': - return 'k'; + return Optional.of('k'); case 'Ĺ': - return 'l'; + return Optional.of('l'); case 'ĺ': - return 'l'; + return Optional.of('l'); case 'Ļ': - return 'l'; + return Optional.of('l'); case 'ļ': - return 'l'; + return Optional.of('l'); case 'Ľ': - return 'l'; + return Optional.of('l'); case 'ľ': - return 'l'; + return Optional.of('l'); case 'Ŀ': - return 'l'; + return Optional.of('l'); case 'ŀ': - return 'l'; + return Optional.of('l'); case 'Ł': - return 'l'; + return Optional.of('l'); case 'ł': - return 'l'; + return Optional.of('l'); case 'Ń': - return 'n'; + return Optional.of('n'); case 'ń': - return 'n'; + return Optional.of('n'); case 'Ņ': - return 'n'; + return Optional.of('n'); case 'ņ': - return 'n'; + return Optional.of('n'); case 'Ň': - return 'n'; + return Optional.of('n'); case 'ň': - return 'n'; + return Optional.of('n'); case 'Ō': - return 'o'; + return Optional.of('o'); case 'ō': - return 'o'; + return Optional.of('o'); case 'Ŏ': - return 'o'; + return Optional.of('o'); case 'ŏ': - return 'o'; + return Optional.of('o'); case 'Ő': - return 'o'; + return Optional.of('o'); case 'ő': - return 'o'; + return Optional.of('o'); case 'Ŕ': - return 'r'; + return Optional.of('r'); case 'ŕ': - return 'r'; + return Optional.of('r'); case 'Ŗ': - return 'r'; + return Optional.of('r'); case 'ŗ': - return 'r'; + return Optional.of('r'); case 'Ř': - return 'r'; + return Optional.of('r'); case 'ř': - return 'r'; + return Optional.of('r'); case 'Ś': - return 's'; + return Optional.of('s'); case 'ś': - return 's'; + return Optional.of('s'); case 'Ŝ': - return 's'; + return Optional.of('s'); case 'ŝ': - return 's'; + return Optional.of('s'); case 'Ş': - return 's'; + return Optional.of('s'); case 'ş': - return 's'; + return Optional.of('s'); case 'Š': - return 's'; + return Optional.of('s'); case 'š': - return 's'; + return Optional.of('s'); case 'Ţ': - return 't'; + return Optional.of('t'); case 'ţ': - return 't'; + return Optional.of('t'); case 'Ť': - return 't'; + return Optional.of('t'); case 'ť': - return 't'; + return Optional.of('t'); case 'Ŧ': - return 't'; + return Optional.of('t'); case 'ŧ': - return 't'; + return Optional.of('t'); case 'Ũ': - return 'u'; + return Optional.of('u'); case 'ũ': - return 'u'; + return Optional.of('u'); case 'Ū': - return 'u'; + return Optional.of('u'); case 'ū': - return 'u'; + return Optional.of('u'); case 'Ŭ': - return 'u'; + return Optional.of('u'); case 'ŭ': - return 'u'; + return Optional.of('u'); case 'Ů': - return 'u'; + return Optional.of('u'); case 'ů': - return 'u'; + return Optional.of('u'); case 'Ű': - return 'u'; + return Optional.of('u'); case 'ű': - return 'u'; + return Optional.of('u'); case 'Ų': - return 'u'; + return Optional.of('u'); case 'ų': - return 'u'; + return Optional.of('u'); case 'Ŵ': - return 'w'; + return Optional.of('w'); case 'ŵ': - return 'w'; + return Optional.of('w'); case 'Ŷ': - return 'y'; + return Optional.of('y'); case 'ŷ': - return 'y'; + return Optional.of('y'); case 'Ÿ': - return 'y'; + return Optional.of('y'); case 'Ź': - return 'z'; + return Optional.of('z'); case 'ź': - return 'z'; + return Optional.of('z'); case 'Ż': - return 'z'; + return Optional.of('z'); case 'ż': - return 'z'; + return Optional.of('z'); case 'Ž': - return 'z'; + return Optional.of('z'); case 'ž': - return 'z'; + return Optional.of('z'); case 'ſ': - return 's'; + return Optional.of('s'); case 'ƀ': - return 'b'; + return Optional.of('b'); case 'Ɓ': - return 'b'; + return Optional.of('b'); case 'Ƃ': - return 'b'; + return Optional.of('b'); case 'ƃ': - return 'b'; + return Optional.of('b'); case 'Ɔ': - return 'o'; + return Optional.of('o'); case 'Ƈ': - return 'c'; + return Optional.of('c'); case 'ƈ': - return 'c'; + return Optional.of('c'); case 'Ɖ': - return 'd'; + return Optional.of('d'); case 'Ɗ': - return 'd'; + return Optional.of('d'); case 'Ƌ': - return 'd'; + return Optional.of('d'); case 'ƌ': - return 'd'; + return Optional.of('d'); case 'ƍ': - return 'd'; + return Optional.of('d'); case 'Ɛ': - return 'e'; + return Optional.of('e'); case 'Ƒ': - return 'f'; + return Optional.of('f'); case 'ƒ': - return 'f'; + return Optional.of('f'); case 'Ɠ': - return 'g'; + return Optional.of('g'); case 'Ɣ': - return 'g'; + return Optional.of('g'); case 'Ɩ': - return 'i'; + return Optional.of('i'); case 'Ɨ': - return 'i'; + return Optional.of('i'); case 'Ƙ': - return 'k'; + return Optional.of('k'); case 'ƙ': - return 'k'; + return Optional.of('k'); case 'ƚ': - return 'l'; + return Optional.of('l'); case 'ƛ': - return 'l'; + return Optional.of('l'); case 'Ɯ': - return 'w'; + return Optional.of('w'); case 'Ɲ': - return 'n'; + return Optional.of('n'); case 'ƞ': - return 'n'; + return Optional.of('n'); case 'Ɵ': - return 'o'; + return Optional.of('o'); case 'Ơ': - return 'o'; + return Optional.of('o'); case 'ơ': - return 'o'; + return Optional.of('o'); case 'Ƥ': - return 'p'; + return Optional.of('p'); case 'ƥ': - return 'p'; + return Optional.of('p'); case 'ƫ': - return 't'; + return Optional.of('t'); case 'Ƭ': - return 't'; + return Optional.of('t'); case 'ƭ': - return 't'; + return Optional.of('t'); case 'Ʈ': - return 't'; + return Optional.of('t'); case 'Ư': - return 'u'; + return Optional.of('u'); case 'ư': - return 'u'; + return Optional.of('u'); case 'Ʊ': - return 'y'; + return Optional.of('y'); case 'Ʋ': - return 'v'; + return Optional.of('v'); case 'Ƴ': - return 'y'; + return Optional.of('y'); case 'ƴ': - return 'y'; + return Optional.of('y'); case 'Ƶ': - return 'z'; + return Optional.of('z'); case 'ƶ': - return 'z'; + return Optional.of('z'); case 'ƿ': - return 'w'; + return Optional.of('w'); case 'Ǎ': - return 'a'; + return Optional.of('a'); case 'ǎ': - return 'a'; + return Optional.of('a'); case 'Ǐ': - return 'i'; + return Optional.of('i'); case 'ǐ': - return 'i'; + return Optional.of('i'); case 'Ǒ': - return 'o'; + return Optional.of('o'); case 'ǒ': - return 'o'; + return Optional.of('o'); case 'Ǔ': - return 'u'; + return Optional.of('u'); case 'ǔ': - return 'u'; + return Optional.of('u'); case 'Ǖ': - return 'u'; + return Optional.of('u'); case 'ǖ': - return 'u'; + return Optional.of('u'); case 'Ǘ': - return 'u'; + return Optional.of('u'); case 'ǘ': - return 'u'; + return Optional.of('u'); case 'Ǚ': - return 'u'; + return Optional.of('u'); case 'ǚ': - return 'u'; + return Optional.of('u'); case 'Ǜ': - return 'u'; + return Optional.of('u'); case 'ǜ': - return 'u'; + return Optional.of('u'); case 'Ǟ': - return 'a'; + return Optional.of('a'); case 'ǟ': - return 'a'; + return Optional.of('a'); case 'Ǡ': - return 'a'; + return Optional.of('a'); case 'ǡ': - return 'a'; + return Optional.of('a'); case 'Ǥ': - return 'g'; + return Optional.of('g'); case 'ǥ': - return 'g'; + return Optional.of('g'); case 'Ǧ': - return 'g'; + return Optional.of('g'); case 'ǧ': - return 'g'; + return Optional.of('g'); case 'Ǩ': - return 'k'; + return Optional.of('k'); case 'ǩ': - return 'k'; + return Optional.of('k'); case 'Ǫ': - return 'o'; + return Optional.of('o'); case 'ǫ': - return 'o'; + return Optional.of('o'); case 'Ǭ': - return 'o'; + return Optional.of('o'); case 'ǭ': - return 'o'; + return Optional.of('o'); case 'ǰ': - return 'j'; + return Optional.of('j'); case 'Dz': - return 'd'; + return Optional.of('d'); case 'Ǵ': - return 'g'; + return Optional.of('g'); case 'ǵ': - return 'g'; + return Optional.of('g'); case 'Ƿ': - return 'w'; + return Optional.of('w'); case 'Ǹ': - return 'n'; + return Optional.of('n'); case 'ǹ': - return 'n'; + return Optional.of('n'); case 'Ǻ': - return 'a'; + return Optional.of('a'); case 'ǻ': - return 'a'; + return Optional.of('a'); case 'Ǿ': - return 'o'; + return Optional.of('o'); case 'ǿ': - return 'o'; + return Optional.of('o'); case 'Ȁ': - return 'a'; + return Optional.of('a'); case 'ȁ': - return 'a'; + return Optional.of('a'); case 'Ȃ': - return 'a'; + return Optional.of('a'); case 'ȃ': - return 'a'; + return Optional.of('a'); case 'Ȅ': - return 'e'; + return Optional.of('e'); case 'ȅ': - return 'e'; + return Optional.of('e'); case 'Ȇ': - return 'e'; + return Optional.of('e'); case 'ȇ': - return 'e'; + return Optional.of('e'); case 'Ȉ': - return 'i'; + return Optional.of('i'); case 'ȉ': - return 'i'; + return Optional.of('i'); case 'Ȋ': - return 'i'; + return Optional.of('i'); case 'ȋ': - return 'i'; + return Optional.of('i'); case 'Ȍ': - return 'o'; + return Optional.of('o'); case 'ȍ': - return 'o'; + return Optional.of('o'); case 'Ȏ': - return 'o'; + return Optional.of('o'); case 'ȏ': - return 'o'; + return Optional.of('o'); case 'Ȑ': - return 'r'; + return Optional.of('r'); case 'ȑ': - return 'r'; + return Optional.of('r'); case 'Ȓ': - return 'r'; + return Optional.of('r'); case 'ȓ': - return 'r'; + return Optional.of('r'); case 'Ȕ': - return 'u'; + return Optional.of('u'); case 'ȕ': - return 'u'; + return Optional.of('u'); case 'Ȗ': - return 'u'; + return Optional.of('u'); case 'ȗ': - return 'u'; + return Optional.of('u'); case 'Ș': - return 's'; + return Optional.of('s'); case 'ș': - return 's'; + return Optional.of('s'); case 'Ț': - return 't'; + return Optional.of('t'); case 'ț': - return 't'; + return Optional.of('t'); case 'Ȝ': - return 'y'; + return Optional.of('y'); case 'ȝ': - return 'y'; + return Optional.of('y'); case 'Ȟ': - return 'h'; + return Optional.of('h'); case 'ȟ': - return 'h'; + return Optional.of('h'); case 'Ȥ': - return 'z'; + return Optional.of('z'); case 'ȥ': - return 'z'; + return Optional.of('z'); case 'Ȧ': - return 'a'; + return Optional.of('a'); case 'ȧ': - return 'a'; + return Optional.of('a'); case 'Ȩ': - return 'e'; + return Optional.of('e'); case 'ȩ': - return 'e'; + return Optional.of('e'); case 'Ȫ': - return 'o'; + return Optional.of('o'); case 'ȫ': - return 'o'; + return Optional.of('o'); case 'Ȭ': - return 'o'; + return Optional.of('o'); case 'ȭ': - return 'o'; + return Optional.of('o'); case 'Ȯ': - return 'o'; + return Optional.of('o'); case 'ȯ': - return 'o'; + return Optional.of('o'); case 'Ȱ': - return 'o'; + return Optional.of('o'); case 'ȱ': - return 'o'; + return Optional.of('o'); case 'Ȳ': - return 'y'; + return Optional.of('y'); case 'ȳ': - return 'y'; + return Optional.of('y'); case 'A': - return 'a'; + return Optional.of('a'); case 'B': - return 'b'; + return Optional.of('b'); case 'C': - return 'c'; + return Optional.of('c'); case 'D': - return 'd'; + return Optional.of('d'); case 'E': - return 'e'; + return Optional.of('e'); case 'F': - return 'f'; + return Optional.of('f'); case 'G': - return 'g'; + return Optional.of('g'); case 'H': - return 'h'; + return Optional.of('h'); case 'I': - return 'i'; + return Optional.of('i'); case 'J': - return 'j'; + return Optional.of('j'); case 'K': - return 'k'; + return Optional.of('k'); case 'L': - return 'l'; + return Optional.of('l'); case 'M': - return 'm'; + return Optional.of('m'); case 'N': - return 'n'; + return Optional.of('n'); case 'O': - return 'o'; + return Optional.of('o'); case 'P': - return 'p'; + return Optional.of('p'); case 'Q': - return 'q'; + return Optional.of('q'); case 'R': - return 'r'; + return Optional.of('r'); case 'S': - return 's'; + return Optional.of('s'); case 'T': - return 't'; + return Optional.of('t'); case 'U': - return 'u'; + return Optional.of('u'); case 'V': - return 'v'; + return Optional.of('v'); case 'W': - return 'w'; + return Optional.of('w'); case 'X': - return 'x'; + return Optional.of('x'); case 'Y': - return 'y'; + return Optional.of('y'); case 'Z': - return 'z'; + return Optional.of('z'); default: - return ch; + return Optional.absent(); } } @Override - public byte getDialpadIndex(char ch) { - if (ch >= '0' && ch <= '9') { - return (byte) (ch - '0'); - } else if (ch >= 'a' && ch <= 'z') { - return (byte) (LATIN_LETTERS_TO_DIGITS[ch - 'a'] - '0'); - } else { - return -1; - } - } - - @Override - public char getDialpadNumericCharacter(char ch) { - if (ch >= 'a' && ch <= 'z') { - return LATIN_LETTERS_TO_DIGITS[ch - 'a']; - } - return ch; + SimpleArrayMap getCharToKeyMap() { + return CHAR_TO_KEY_MAP; } } diff --git a/java/com/android/dialer/smartdial/RussianSmartDialMap.java b/java/com/android/dialer/smartdial/RussianSmartDialMap.java new file mode 100644 index 000000000..ada9182e1 --- /dev/null +++ b/java/com/android/dialer/smartdial/RussianSmartDialMap.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017 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.dialer.smartdial; + +import android.support.v4.util.SimpleArrayMap; +import com.google.common.base.Optional; + +/** A {@link SmartDialMap} for the Russian alphabet. */ +@SuppressWarnings("Guava") +final class RussianSmartDialMap extends SmartDialMap { + private static final SimpleArrayMap CHAR_TO_KEY_MAP = + new SimpleArrayMap<>(); + + // Reference: https://en.wikipedia.org/wiki/Russian_alphabet + static { + CHAR_TO_KEY_MAP.put('а', '2'); + CHAR_TO_KEY_MAP.put('б', '2'); + CHAR_TO_KEY_MAP.put('в', '2'); + CHAR_TO_KEY_MAP.put('г', '2'); + + CHAR_TO_KEY_MAP.put('д', '3'); + CHAR_TO_KEY_MAP.put('е', '3'); + CHAR_TO_KEY_MAP.put('ё', '3'); + CHAR_TO_KEY_MAP.put('ж', '3'); + CHAR_TO_KEY_MAP.put('з', '3'); + + CHAR_TO_KEY_MAP.put('и', '4'); + CHAR_TO_KEY_MAP.put('й', '4'); + CHAR_TO_KEY_MAP.put('к', '4'); + CHAR_TO_KEY_MAP.put('л', '4'); + + CHAR_TO_KEY_MAP.put('м', '5'); + CHAR_TO_KEY_MAP.put('н', '5'); + CHAR_TO_KEY_MAP.put('о', '5'); + CHAR_TO_KEY_MAP.put('п', '5'); + + CHAR_TO_KEY_MAP.put('р', '6'); + CHAR_TO_KEY_MAP.put('с', '6'); + CHAR_TO_KEY_MAP.put('т', '6'); + CHAR_TO_KEY_MAP.put('у', '6'); + + CHAR_TO_KEY_MAP.put('ф', '7'); + CHAR_TO_KEY_MAP.put('х', '7'); + CHAR_TO_KEY_MAP.put('ц', '7'); + CHAR_TO_KEY_MAP.put('ч', '7'); + + CHAR_TO_KEY_MAP.put('ш', '8'); + CHAR_TO_KEY_MAP.put('щ', '8'); + CHAR_TO_KEY_MAP.put('ъ', '8'); + CHAR_TO_KEY_MAP.put('ы', '8'); + + CHAR_TO_KEY_MAP.put('ь', '9'); + CHAR_TO_KEY_MAP.put('э', '9'); + CHAR_TO_KEY_MAP.put('ю', '9'); + CHAR_TO_KEY_MAP.put('я', '9'); + } + + private static RussianSmartDialMap instance; + + static RussianSmartDialMap getInstance() { + if (instance == null) { + instance = new RussianSmartDialMap(); + } + + return instance; + } + + private RussianSmartDialMap() {} + + @Override + Optional normalizeCharacter(char ch) { + ch = Character.toLowerCase(ch); + return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent(); + } + + @Override + SimpleArrayMap getCharToKeyMap() { + return CHAR_TO_KEY_MAP; + } +} diff --git a/java/com/android/dialer/smartdial/SmartDialMap.java b/java/com/android/dialer/smartdial/SmartDialMap.java index 9638929a6..bc5c9ea72 100644 --- a/java/com/android/dialer/smartdial/SmartDialMap.java +++ b/java/com/android/dialer/smartdial/SmartDialMap.java @@ -16,45 +16,88 @@ package com.android.dialer.smartdial; -/** - * Note: These methods currently take characters as arguments. For future planned language support, - * they will need to be changed to use codepoints instead of characters. - * - *

http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#codePointAt(int) - * - *

If/when this change is made, LatinSmartDialMap(which operates on chars) will continue to work - * by simply casting from a codepoint to a character. - */ -public interface SmartDialMap { +import android.support.v4.util.SimpleArrayMap; +import com.google.common.base.Optional; + +/** Definition for utilities that supports smart dial in different languages. */ +@SuppressWarnings("Guava") +abstract class SmartDialMap { - /* - * Returns true if the provided character can be mapped to a key on the dialpad + /** + * Returns true if the provided character can be mapped to a key on the dialpad. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. */ - boolean isValidDialpadCharacter(char ch); + protected boolean isValidDialpadCharacter(char ch) { + return isValidDialpadAlphabeticChar(ch) || isValidDialpadNumericChar(ch); + } - /* - * Returns true if the provided character is a letter, and can be mapped to a key on the dialpad + /** + * Returns true if the provided character is a letter and can be mapped to a key on the dialpad. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. */ - boolean isValidDialpadAlphabeticChar(char ch); + protected boolean isValidDialpadAlphabeticChar(char ch) { + return getCharToKeyMap().containsKey(ch); + } - /* - * Returns true if the provided character is a digit, and can be mapped to a key on the dialpad + /** + * Returns true if the provided character is a digit, and can be mapped to a key on the dialpad. */ - boolean isValidDialpadNumericChar(char ch); + protected boolean isValidDialpadNumericChar(char ch) { + return '0' <= ch && ch <= '9'; + } - /* - * Get the index of the key on the dialpad which the character corresponds to + /** + * Get the index of the key on the dialpad which the character corresponds to. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. + * + *

An {@link Optional#absent()} is returned if the provided character can't be mapped to a key + * on the dialpad. */ - byte getDialpadIndex(char ch); + protected Optional getDialpadIndex(char ch) { + if (isValidDialpadNumericChar(ch)) { + return Optional.of((byte) (ch - '0')); + } - /* - * Get the actual numeric character on the dialpad which the character corresponds to + if (isValidDialpadAlphabeticChar(ch)) { + return Optional.of((byte) (getCharToKeyMap().get(ch) - '0')); + } + + return Optional.absent(); + } + + /** + * Get the actual numeric character on the dialpad which the character corresponds to. + * + *

The provided character is expected to be a normalized character. See {@link + * SmartDialMap#normalizeCharacter(char)} for details. + * + *

An {@link Optional#absent()} is returned if the provided character can't be mapped to a key + * on the dialpad. */ - char getDialpadNumericCharacter(char ch); + protected Optional getDialpadNumericCharacter(char ch) { + return isValidDialpadAlphabeticChar(ch) + ? Optional.of(getCharToKeyMap().get(ch)) + : Optional.absent(); + } - /* + /** * Converts uppercase characters to lower case ones, and on a best effort basis, strips accents * from accented characters. + * + *

An {@link Optional#absent()} is returned if the provided character can't be mapped to a key + * on the dialpad. + */ + abstract Optional normalizeCharacter(char ch); + + /** + * Returns a map in which each key is a normalized character and the corresponding value is a + * dialpad key. */ - char normalizeCharacter(char ch); + abstract SimpleArrayMap getCharToKeyMap(); } diff --git a/java/com/android/dialer/smartdial/SmartDialNameMatcher.java b/java/com/android/dialer/smartdial/SmartDialNameMatcher.java index a9256ec3f..4e3e0cc3f 100644 --- a/java/com/android/dialer/smartdial/SmartDialNameMatcher.java +++ b/java/com/android/dialer/smartdial/SmartDialNameMatcher.java @@ -16,6 +16,7 @@ package com.android.dialer.smartdial; +import android.content.Context; import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.dialer.smartdial.SmartDialPrefix.PhoneNumberTokens; @@ -29,8 +30,6 @@ import java.util.ArrayList; * (J)ohn (S)mith. */ public class SmartDialNameMatcher { - - public static final SmartDialMap LATIN_SMART_DIAL_MAP = new LatinSmartDialMap(); // Whether or not we allow matches like 57 - (J)ohn (S)mith private static final boolean ALLOW_INITIAL_MATCH = true; @@ -39,15 +38,13 @@ public class SmartDialNameMatcher { private static final int INITIAL_LENGTH_LIMIT = 1; private final ArrayList mMatchPositions = new ArrayList<>(); - private final SmartDialMap mMap; private String mQuery; // Controls whether to treat an empty query as a match (with anything). private boolean mShouldMatchEmptyQuery = false; - public SmartDialNameMatcher(String query, SmartDialMap map) { + public SmartDialNameMatcher(String query) { mQuery = query; - mMap = map; } /** @@ -56,8 +53,8 @@ public class SmartDialNameMatcher { * @param number Phone number we want to normalize * @return Phone number consisting of digits from 0-9 */ - public static String normalizeNumber(String number, SmartDialMap map) { - return normalizeNumber(number, 0, map); + public static String normalizeNumber(Context context, String number) { + return normalizeNumber(context, number, /* offset = */ 0); } /** @@ -67,11 +64,11 @@ public class SmartDialNameMatcher { * @param offset Offset to start from * @return Phone number consisting of digits from 0-9 */ - public static String normalizeNumber(String number, int offset, SmartDialMap map) { + public static String normalizeNumber(Context context, String number, int offset) { final StringBuilder s = new StringBuilder(); for (int i = offset; i < number.length(); i++) { char ch = number.charAt(i); - if (map.isValidDialpadNumericChar(ch)) { + if (CompositeSmartDialMap.isValidDialpadNumericChar(context, ch)) { s.append(ch); } } @@ -112,7 +109,7 @@ public class SmartDialNameMatcher { * with the matching positions otherwise */ @Nullable - public SmartDialMatchPosition matchesNumber(String phoneNumber, String query) { + public SmartDialMatchPosition matchesNumber(Context context, String phoneNumber, String query) { if (TextUtils.isEmpty(phoneNumber)) { return mShouldMatchEmptyQuery ? new SmartDialMatchPosition(0, 0) : null; } @@ -120,15 +117,19 @@ public class SmartDialNameMatcher { constructEmptyMask(builder, phoneNumber.length()); // Try matching the number as is - SmartDialMatchPosition matchPos = matchesNumberWithOffset(phoneNumber, query, 0); + SmartDialMatchPosition matchPos = + matchesNumberWithOffset(context, phoneNumber, query, /* offset = */ 0); if (matchPos == null) { - PhoneNumberTokens phoneNumberTokens = SmartDialPrefix.parsePhoneNumber(phoneNumber); + PhoneNumberTokens phoneNumberTokens = SmartDialPrefix.parsePhoneNumber(context, phoneNumber); if (phoneNumberTokens.countryCodeOffset != 0) { - matchPos = matchesNumberWithOffset(phoneNumber, query, phoneNumberTokens.countryCodeOffset); + matchPos = + matchesNumberWithOffset( + context, phoneNumber, query, phoneNumberTokens.countryCodeOffset); } if (matchPos == null && phoneNumberTokens.nanpCodeOffset != 0) { - matchPos = matchesNumberWithOffset(phoneNumber, query, phoneNumberTokens.nanpCodeOffset); + matchPos = + matchesNumberWithOffset(context, phoneNumber, query, phoneNumberTokens.nanpCodeOffset); } } if (matchPos != null) { @@ -145,8 +146,8 @@ public class SmartDialNameMatcher { * @return {@literal null} if the number and the query don't match, a valid SmartDialMatchPosition * with the matching positions otherwise */ - public SmartDialMatchPosition matchesNumber(String phoneNumber) { - return matchesNumber(phoneNumber, mQuery); + public SmartDialMatchPosition matchesNumber(Context context, String phoneNumber) { + return matchesNumber(context, phoneNumber, mQuery); } /** @@ -160,7 +161,7 @@ public class SmartDialNameMatcher { * with the matching positions otherwise */ private SmartDialMatchPosition matchesNumberWithOffset( - String phoneNumber, String query, int offset) { + Context context, String phoneNumber, String query, int offset) { if (TextUtils.isEmpty(phoneNumber) || TextUtils.isEmpty(query)) { return mShouldMatchEmptyQuery ? new SmartDialMatchPosition(offset, offset) : null; } @@ -171,7 +172,7 @@ public class SmartDialNameMatcher { break; } char ch = phoneNumber.charAt(i); - if (mMap.isValidDialpadNumericChar(ch)) { + if (CompositeSmartDialMap.isValidDialpadNumericChar(context, ch)) { if (ch != query.charAt(queryAt)) { return null; } @@ -225,7 +226,10 @@ public class SmartDialNameMatcher { * match positions (multiple matches correspond to initial matches). */ private boolean matchesCombination( - String displayName, String query, ArrayList matchList) { + Context context, + String displayName, + String query, + ArrayList matchList) { StringBuilder builder = new StringBuilder(); constructEmptyMask(builder, displayName.length()); final int nameLength = displayName.length(); @@ -260,10 +264,10 @@ public class SmartDialNameMatcher { while (nameStart < nameLength && queryStart < queryLength) { char ch = displayName.charAt(nameStart); // Strip diacritics from accented characters if any - ch = mMap.normalizeCharacter(ch); - if (mMap.isValidDialpadCharacter(ch)) { - if (mMap.isValidDialpadAlphabeticChar(ch)) { - ch = mMap.getDialpadNumericCharacter(ch); + ch = CompositeSmartDialMap.normalizeCharacter(context, ch); + if (CompositeSmartDialMap.isValidDialpadCharacter(context, ch)) { + if (CompositeSmartDialMap.isValidDialpadAlphabeticChar(context, ch)) { + ch = CompositeSmartDialMap.getDialpadNumericCharacter(context, ch); } if (ch != query.charAt(queryStart)) { // Failed to match the current character in the query. @@ -283,12 +287,16 @@ public class SmartDialNameMatcher { // then skip to the end of the "Yoghurt" token. if (queryStart == 0 - || mMap.isValidDialpadCharacter( - mMap.normalizeCharacter(displayName.charAt(nameStart - 1)))) { + || CompositeSmartDialMap.isValidDialpadCharacter( + context, + CompositeSmartDialMap.normalizeCharacter( + context, displayName.charAt(nameStart - 1)))) { // skip to the next token, in the case of 1 or 2. while (nameStart < nameLength - && mMap.isValidDialpadCharacter( - mMap.normalizeCharacter(displayName.charAt(nameStart)))) { + && CompositeSmartDialMap.isValidDialpadCharacter( + context, + CompositeSmartDialMap.normalizeCharacter( + context, displayName.charAt(nameStart)))) { nameStart++; } nameStart++; @@ -316,7 +324,9 @@ public class SmartDialNameMatcher { // find the next separator in the query string int j; for (j = nameStart; j < nameLength; j++) { - if (!mMap.isValidDialpadCharacter(mMap.normalizeCharacter(displayName.charAt(j)))) { + if (!CompositeSmartDialMap.isValidDialpadCharacter( + context, + CompositeSmartDialMap.normalizeCharacter(context, displayName.charAt(j)))) { break; } } @@ -324,7 +334,8 @@ public class SmartDialNameMatcher { if (j < nameLength - 1) { final String remainder = displayName.substring(j + 1); final ArrayList partialTemp = new ArrayList<>(); - if (matchesCombination(remainder, query.substring(queryStart + 1), partialTemp)) { + if (matchesCombination( + context, remainder, query.substring(queryStart + 1), partialTemp)) { // store the list of possible match positions SmartDialMatchPosition.advanceMatchPositions(partialTemp, j + 1); @@ -393,9 +404,9 @@ public class SmartDialNameMatcher { * contained in query. If the function returns true, matchList will contain an ArrayList of * match positions (multiple matches correspond to initial matches). */ - public boolean matches(String displayName) { + public boolean matches(Context context, String displayName) { mMatchPositions.clear(); - return matchesCombination(displayName, mQuery, mMatchPositions); + return matchesCombination(context, displayName, mQuery, mMatchPositions); } public ArrayList getMatchPositions() { diff --git a/java/com/android/dialer/smartdial/SmartDialPrefix.java b/java/com/android/dialer/smartdial/SmartDialPrefix.java index 36f174b33..b9c1f8c11 100644 --- a/java/com/android/dialer/smartdial/SmartDialPrefix.java +++ b/java/com/android/dialer/smartdial/SmartDialPrefix.java @@ -52,8 +52,6 @@ public class SmartDialPrefix { "DialtactsActivity_user_sim_country_code"; private static final String PREF_USER_SIM_COUNTRY_CODE_DEFAULT = null; - /** Dialpad mapping. */ - private static final SmartDialMap mMap = new LatinSmartDialMap(); private static String sUserSimCountryCode = PREF_USER_SIM_COUNTRY_CODE_DEFAULT; /** Indicates whether user is in NANP regions. */ @@ -95,7 +93,7 @@ public class SmartDialPrefix { * @param contactName Contact's name stored in string. * @return A list of name tokens, for example separated first names, last name, etc. */ - public static ArrayList parseToIndexTokens(String contactName) { + public static ArrayList parseToIndexTokens(Context context, String contactName) { final int length = contactName.length(); final ArrayList result = new ArrayList<>(); char c; @@ -106,10 +104,10 @@ public class SmartDialPrefix { * " ", mark the current token as complete and add it to the list of tokens. */ for (int i = 0; i < length; i++) { - c = mMap.normalizeCharacter(contactName.charAt(i)); - if (mMap.isValidDialpadCharacter(c)) { + c = CompositeSmartDialMap.normalizeCharacter(context, contactName.charAt(i)); + if (CompositeSmartDialMap.isValidDialpadCharacter(context, c)) { /** Converts a character into the number on dialpad that represents the character. */ - currentIndexToken.append(mMap.getDialpadIndex(c)); + currentIndexToken.append(CompositeSmartDialMap.getDialpadIndex(context, c)); } else { if (currentIndexToken.length() != 0) { result.add(currentIndexToken.toString()); @@ -132,11 +130,11 @@ public class SmartDialPrefix { * @param index The contact's name in string. * @return A List of strings, whose prefix can be used to look up the contact. */ - public static ArrayList generateNamePrefixes(String index) { + public static ArrayList generateNamePrefixes(Context context, String index) { final ArrayList result = new ArrayList<>(); /** Parses the name into a list of tokens. */ - final ArrayList indexTokens = parseToIndexTokens(index); + final ArrayList indexTokens = parseToIndexTokens(context, index); if (indexTokens.size() > 0) { /** @@ -198,13 +196,13 @@ public class SmartDialPrefix { * @param number String of user's phone number. * @return A list of strings where any prefix of any entry can be used to look up the number. */ - public static ArrayList parseToNumberTokens(String number) { + public static ArrayList parseToNumberTokens(Context context, String number) { final ArrayList result = new ArrayList<>(); if (!TextUtils.isEmpty(number)) { /** Adds the full number to the list. */ - result.add(SmartDialNameMatcher.normalizeNumber(number, mMap)); + result.add(SmartDialNameMatcher.normalizeNumber(context, number)); - final PhoneNumberTokens phoneNumberTokens = parsePhoneNumber(number); + final PhoneNumberTokens phoneNumberTokens = parsePhoneNumber(context, number); if (phoneNumberTokens == null) { return result; } @@ -212,12 +210,13 @@ public class SmartDialPrefix { if (phoneNumberTokens.countryCodeOffset != 0) { result.add( SmartDialNameMatcher.normalizeNumber( - number, phoneNumberTokens.countryCodeOffset, mMap)); + context, number, phoneNumberTokens.countryCodeOffset)); } if (phoneNumberTokens.nanpCodeOffset != 0) { result.add( - SmartDialNameMatcher.normalizeNumber(number, phoneNumberTokens.nanpCodeOffset, mMap)); + SmartDialNameMatcher.normalizeNumber( + context, number, phoneNumberTokens.nanpCodeOffset)); } } return result; @@ -229,13 +228,13 @@ public class SmartDialPrefix { * @param number Raw phone number. * @return a PhoneNumberToken instance with country code, NANP code information. */ - public static PhoneNumberTokens parsePhoneNumber(String number) { + public static PhoneNumberTokens parsePhoneNumber(Context context, String number) { String countryCode = ""; int countryCodeOffset = 0; int nanpNumberOffset = 0; if (!TextUtils.isEmpty(number)) { - String normalizedNumber = SmartDialNameMatcher.normalizeNumber(number, mMap); + String normalizedNumber = SmartDialNameMatcher.normalizeNumber(context, number); if (number.charAt(0) == '+') { /** If the number starts with '+', tries to find valid country code. */ for (int i = 1; i <= 1 + 3; i++) { @@ -518,10 +517,6 @@ public class SmartDialPrefix { return result; } - public static SmartDialMap getMap() { - return mMap; - } - /** * Indicates whether the given country uses NANP numbers * -- cgit v1.2.3 From e12f924c45d9e40dbdd23ad01b7bde855b82c4c8 Mon Sep 17 00:00:00 2001 From: roldenburg Date: Tue, 19 Dec 2017 13:33:45 -0800 Subject: Add flags for the Duo install, activate and invite buttons Bug: 70034799 Test: GoogleCallLogAdapterTest PiperOrigin-RevId: 179596742 Change-Id: Ic5616af0680e36786ae5261813eae71cf096b084 --- .../app/calllog/CallLogListItemViewHolder.java | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'java/com/android/dialer') diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index 2baf117dc..15c4b5850 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -691,15 +691,25 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder videoCallButtonView.setTag(IntentProvider.getDuoVideoIntentProvider(number)); videoCallButtonView.setVisibility(View.VISIBLE); } else if (duo.isActivated(mContext) && !identifiedSpamCall) { - inviteVideoButtonView.setTag(IntentProvider.getDuoInviteIntentProvider(number)); - inviteVideoButtonView.setVisibility(View.VISIBLE); + if (ConfigProviderBindings.get(mContext) + .getBoolean("enable_call_log_duo_invite_button", false)) { + inviteVideoButtonView.setTag(IntentProvider.getDuoInviteIntentProvider(number)); + inviteVideoButtonView.setVisibility(View.VISIBLE); + } } else if (duo.isEnabled(mContext) && !identifiedSpamCall) { if (!duo.isInstalled(mContext)) { - setUpVideoButtonView.setTag(IntentProvider.getInstallDuoIntentProvider()); + if (ConfigProviderBindings.get(mContext) + .getBoolean("enable_call_log_install_duo_button", false)) { + setUpVideoButtonView.setTag(IntentProvider.getInstallDuoIntentProvider()); + setUpVideoButtonView.setVisibility(View.VISIBLE); + } } else { - setUpVideoButtonView.setTag(IntentProvider.getSetUpDuoIntentProvider()); + if (ConfigProviderBindings.get(mContext) + .getBoolean("enable_call_log_activate_duo_button", false)) { + setUpVideoButtonView.setTag(IntentProvider.getSetUpDuoIntentProvider()); + setUpVideoButtonView.setVisibility(View.VISIBLE); + } } - setUpVideoButtonView.setVisibility(View.VISIBLE); } break; default: -- cgit v1.2.3