summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/smartdial/map
diff options
context:
space:
mode:
authorlinyuh <linyuh@google.com>2017-12-21 15:42:00 -0800
committerEric Erfanian <erfanian@google.com>2017-12-22 08:57:09 -0800
commitbf4bb0555ca827e660ad05b4caf982a030211c03 (patch)
treeacbccec794536c7d06985f8f7f00475004e0eeed /java/com/android/dialer/smartdial/map
parent507340c5b86799320454811b3fca1b8534028fb1 (diff)
Reorganize classes related to smart dial.
Bug: 30215380,70633239 Test: Existing tests PiperOrigin-RevId: 179868033 Change-Id: If8cdbdfafb3a66397623578131649cb8adc18733
Diffstat (limited to 'java/com/android/dialer/smartdial/map')
-rw-r--r--java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java91
-rw-r--r--java/com/android/dialer/smartdial/map/CompositeSmartDialMap.java172
-rw-r--r--java/com/android/dialer/smartdial/map/LatinSmartDialMap.java785
-rw-r--r--java/com/android/dialer/smartdial/map/RussianSmartDialMap.java94
-rw-r--r--java/com/android/dialer/smartdial/map/SmartDialMap.java103
-rw-r--r--java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java93
6 files changed, 1338 insertions, 0 deletions
diff --git a/java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java b/java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java
new file mode 100644
index 000000000..cbe6afa97
--- /dev/null
+++ b/java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java
@@ -0,0 +1,91 @@
+/*
+ * 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.map;
+
+import android.support.v4.util.SimpleArrayMap;
+import com.google.common.base.Optional;
+
+/** A {@link SmartDialMap} for the Bulgarian alphabet. */
+@SuppressWarnings("Guava")
+final class BulgarianSmartDialMap extends SmartDialMap {
+ private static final SimpleArrayMap<Character, Character> CHAR_TO_KEY_MAP =
+ new SimpleArrayMap<>();
+
+ // Reference: https://en.wikipedia.org/wiki/Bulgarian_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('и', '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('п', '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 BulgarianSmartDialMap instance;
+
+ static BulgarianSmartDialMap getInstance() {
+ if (instance == null) {
+ instance = new BulgarianSmartDialMap();
+ }
+
+ return instance;
+ }
+
+ private BulgarianSmartDialMap() {}
+
+ @Override
+ Optional<Character> normalizeCharacter(char ch) {
+ ch = Character.toLowerCase(ch);
+ return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent();
+ }
+
+ @Override
+ SimpleArrayMap<Character, Character> getCharToKeyMap() {
+ return CHAR_TO_KEY_MAP;
+ }
+}
diff --git a/java/com/android/dialer/smartdial/map/CompositeSmartDialMap.java b/java/com/android/dialer/smartdial/map/CompositeSmartDialMap.java
new file mode 100644
index 000000000..df32d4ce7
--- /dev/null
+++ b/java/com/android/dialer/smartdial/map/CompositeSmartDialMap.java
@@ -0,0 +1,172 @@
+/*
+ * 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.map;
+
+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.
+ *
+ * <p>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.
+ *
+ * <p>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<String, SmartDialMap> EXTRA_MAPS = new SimpleArrayMap<>();
+
+ static {
+ EXTRA_MAPS.put("bul", BulgarianSmartDialMap.getInstance());
+ EXTRA_MAPS.put("rus", RussianSmartDialMap.getInstance());
+ EXTRA_MAPS.put("ukr", UkrainianSmartDialMap.getInstance());
+ }
+
+ private CompositeSmartDialMap() {}
+
+ /**
+ * Returns true if the provided character can be mapped to a key on the dialpad.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ */
+ public static boolean isValidDialpadCharacter(Context context, char ch) {
+ if (DEFAULT_MAP.isValidDialpadCharacter(ch)) {
+ return true;
+ }
+
+ Optional<SmartDialMap> 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.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ */
+ public static boolean isValidDialpadAlphabeticChar(Context context, char ch) {
+ if (DEFAULT_MAP.isValidDialpadAlphabeticChar(ch)) {
+ return true;
+ }
+
+ Optional<SmartDialMap> 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.
+ */
+ public static boolean isValidDialpadNumericChar(Context context, char ch) {
+ if (DEFAULT_MAP.isValidDialpadNumericChar(ch)) {
+ return true;
+ }
+
+ Optional<SmartDialMap> extraMap = getExtraMap(context);
+ return extraMap.isPresent() && extraMap.get().isValidDialpadNumericChar(ch);
+ }
+
+ /**
+ * Get the index of the key on the dialpad which the character corresponds to.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ *
+ * <p>If the provided character can't be mapped to a key on the dialpad, return -1.
+ */
+ public static byte getDialpadIndex(Context context, char ch) {
+ Optional<Byte> dialpadIndex = DEFAULT_MAP.getDialpadIndex(ch);
+ if (dialpadIndex.isPresent()) {
+ return dialpadIndex.get();
+ }
+
+ Optional<SmartDialMap> 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.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ *
+ * <p>If the provided character can't be mapped to a key on the dialpad, return the character.
+ */
+ public static char getDialpadNumericCharacter(Context context, char ch) {
+ Optional<Character> dialpadNumericChar = DEFAULT_MAP.getDialpadNumericCharacter(ch);
+ if (dialpadNumericChar.isPresent()) {
+ return dialpadNumericChar.get();
+ }
+
+ Optional<SmartDialMap> 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.
+ *
+ * <p>If the provided character can't be mapped to a key on the dialpad, return the character.
+ */
+ public static char normalizeCharacter(Context context, char ch) {
+ Optional<Character> normalizedChar = DEFAULT_MAP.normalizeCharacter(ch);
+ if (normalizedChar.isPresent()) {
+ return normalizedChar.get();
+ }
+
+ Optional<SmartDialMap> extraMap = getExtraMap(context);
+ if (extraMap.isPresent()) {
+ normalizedChar = extraMap.get().normalizeCharacter(ch);
+ }
+
+ return normalizedChar.isPresent() ? normalizedChar.get() : ch;
+ }
+
+ @VisibleForTesting
+ static Optional<SmartDialMap> 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/map/LatinSmartDialMap.java b/java/com/android/dialer/smartdial/map/LatinSmartDialMap.java
new file mode 100644
index 000000000..052af05c0
--- /dev/null
+++ b/java/com/android/dialer/smartdial/map/LatinSmartDialMap.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2016 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.map;
+
+import android.support.v4.util.SimpleArrayMap;
+import com.google.common.base.Optional;
+
+/** A {@link SmartDialMap} for the Latin alphabet, which is for T9 dialpad searching. */
+@SuppressWarnings("Guava")
+final class LatinSmartDialMap extends SmartDialMap {
+ private static final SimpleArrayMap<Character, Character> CHAR_TO_KEY_MAP =
+ new SimpleArrayMap<>();
+
+ static {
+ CHAR_TO_KEY_MAP.put('a', '2');
+ CHAR_TO_KEY_MAP.put('b', '2');
+ CHAR_TO_KEY_MAP.put('c', '2');
+
+ 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');
+ }
+
+ 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
+ * for i in range(192, 564):
+ * char = unichr(i)
+ * decoded = unidecode(char)
+ * # 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 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:
+ * http://pypi.python.org/pypi/Unidecode/0.04.1
+ *
+ * Also remaps all upper case latin characters to their lower case equivalents.
+ */
+ @Override
+ Optional<Character> normalizeCharacter(char ch) {
+ if (isValidDialpadAlphabeticChar(ch)) {
+ return Optional.of(ch);
+ }
+
+ switch (ch) {
+ case 'À':
+ return Optional.of('a');
+ case 'Á':
+ return Optional.of('a');
+ case 'Â':
+ return Optional.of('a');
+ case 'Ã':
+ return Optional.of('a');
+ case 'Ä':
+ return Optional.of('a');
+ case 'Å':
+ return Optional.of('a');
+ case 'Ç':
+ return Optional.of('c');
+ case 'È':
+ return Optional.of('e');
+ case 'É':
+ return Optional.of('e');
+ case 'Ê':
+ return Optional.of('e');
+ case 'Ë':
+ return Optional.of('e');
+ case 'Ì':
+ return Optional.of('i');
+ case 'Í':
+ return Optional.of('i');
+ case 'Î':
+ return Optional.of('i');
+ case 'Ï':
+ return Optional.of('i');
+ case 'Ð':
+ return Optional.of('d');
+ case 'Ñ':
+ return Optional.of('n');
+ case 'Ò':
+ return Optional.of('o');
+ case 'Ó':
+ return Optional.of('o');
+ case 'Ô':
+ return Optional.of('o');
+ case 'Õ':
+ return Optional.of('o');
+ case 'Ö':
+ return Optional.of('o');
+ case '×':
+ return Optional.of('x');
+ case 'Ø':
+ return Optional.of('o');
+ case 'Ù':
+ return Optional.of('u');
+ case 'Ú':
+ return Optional.of('u');
+ case 'Û':
+ return Optional.of('u');
+ case 'Ü':
+ return Optional.of('u');
+ case 'Ý':
+ return Optional.of('u');
+ case 'à':
+ return Optional.of('a');
+ case 'á':
+ return Optional.of('a');
+ case 'â':
+ return Optional.of('a');
+ case 'ã':
+ return Optional.of('a');
+ case 'ä':
+ return Optional.of('a');
+ case 'å':
+ return Optional.of('a');
+ case 'ç':
+ return Optional.of('c');
+ case 'è':
+ return Optional.of('e');
+ case 'é':
+ return Optional.of('e');
+ case 'ê':
+ return Optional.of('e');
+ case 'ë':
+ return Optional.of('e');
+ case 'ì':
+ return Optional.of('i');
+ case 'í':
+ return Optional.of('i');
+ case 'î':
+ return Optional.of('i');
+ case 'ï':
+ return Optional.of('i');
+ case 'ð':
+ return Optional.of('d');
+ case 'ñ':
+ return Optional.of('n');
+ case 'ò':
+ return Optional.of('o');
+ case 'ó':
+ return Optional.of('o');
+ case 'ô':
+ return Optional.of('o');
+ case 'õ':
+ return Optional.of('o');
+ case 'ö':
+ return Optional.of('o');
+ case 'ø':
+ return Optional.of('o');
+ case 'ù':
+ return Optional.of('u');
+ case 'ú':
+ return Optional.of('u');
+ case 'û':
+ return Optional.of('u');
+ case 'ü':
+ return Optional.of('u');
+ case 'ý':
+ return Optional.of('y');
+ case 'ÿ':
+ return Optional.of('y');
+ case 'Ā':
+ return Optional.of('a');
+ case 'ā':
+ return Optional.of('a');
+ case 'Ă':
+ return Optional.of('a');
+ case 'ă':
+ return Optional.of('a');
+ case 'Ą':
+ return Optional.of('a');
+ case 'ą':
+ return Optional.of('a');
+ case 'Ć':
+ return Optional.of('c');
+ case 'ć':
+ return Optional.of('c');
+ case 'Ĉ':
+ return Optional.of('c');
+ case 'ĉ':
+ return Optional.of('c');
+ case 'Ċ':
+ return Optional.of('c');
+ case 'ċ':
+ return Optional.of('c');
+ case 'Č':
+ return Optional.of('c');
+ case 'č':
+ return Optional.of('c');
+ case 'Ď':
+ return Optional.of('d');
+ case 'ď':
+ return Optional.of('d');
+ case 'Đ':
+ return Optional.of('d');
+ case 'đ':
+ return Optional.of('d');
+ case 'Ē':
+ return Optional.of('e');
+ case 'ē':
+ return Optional.of('e');
+ case 'Ĕ':
+ return Optional.of('e');
+ case 'ĕ':
+ return Optional.of('e');
+ case 'Ė':
+ return Optional.of('e');
+ case 'ė':
+ return Optional.of('e');
+ case 'Ę':
+ return Optional.of('e');
+ case 'ę':
+ return Optional.of('e');
+ case 'Ě':
+ return Optional.of('e');
+ case 'ě':
+ return Optional.of('e');
+ case 'Ĝ':
+ return Optional.of('g');
+ case 'ĝ':
+ return Optional.of('g');
+ case 'Ğ':
+ return Optional.of('g');
+ case 'ğ':
+ return Optional.of('g');
+ case 'Ġ':
+ return Optional.of('g');
+ case 'ġ':
+ return Optional.of('g');
+ case 'Ģ':
+ return Optional.of('g');
+ case 'ģ':
+ return Optional.of('g');
+ case 'Ĥ':
+ return Optional.of('h');
+ case 'ĥ':
+ return Optional.of('h');
+ case 'Ħ':
+ return Optional.of('h');
+ case 'ħ':
+ return Optional.of('h');
+ case 'Ĩ':
+ return Optional.of('i');
+ case 'ĩ':
+ return Optional.of('i');
+ case 'Ī':
+ return Optional.of('i');
+ case 'ī':
+ return Optional.of('i');
+ case 'Ĭ':
+ return Optional.of('i');
+ case 'ĭ':
+ return Optional.of('i');
+ case 'Į':
+ return Optional.of('i');
+ case 'į':
+ return Optional.of('i');
+ case 'İ':
+ return Optional.of('i');
+ case 'ı':
+ return Optional.of('i');
+ case 'Ĵ':
+ return Optional.of('j');
+ case 'ĵ':
+ return Optional.of('j');
+ case 'Ķ':
+ return Optional.of('k');
+ case 'ķ':
+ return Optional.of('k');
+ case 'ĸ':
+ return Optional.of('k');
+ case 'Ĺ':
+ return Optional.of('l');
+ case 'ĺ':
+ return Optional.of('l');
+ case 'Ļ':
+ return Optional.of('l');
+ case 'ļ':
+ return Optional.of('l');
+ case 'Ľ':
+ return Optional.of('l');
+ case 'ľ':
+ return Optional.of('l');
+ case 'Ŀ':
+ return Optional.of('l');
+ case 'ŀ':
+ return Optional.of('l');
+ case 'Ł':
+ return Optional.of('l');
+ case 'ł':
+ return Optional.of('l');
+ case 'Ń':
+ return Optional.of('n');
+ case 'ń':
+ return Optional.of('n');
+ case 'Ņ':
+ return Optional.of('n');
+ case 'ņ':
+ return Optional.of('n');
+ case 'Ň':
+ return Optional.of('n');
+ case 'ň':
+ return Optional.of('n');
+ case 'Ō':
+ return Optional.of('o');
+ case 'ō':
+ return Optional.of('o');
+ case 'Ŏ':
+ return Optional.of('o');
+ case 'ŏ':
+ return Optional.of('o');
+ case 'Ő':
+ return Optional.of('o');
+ case 'ő':
+ return Optional.of('o');
+ case 'Ŕ':
+ return Optional.of('r');
+ case 'ŕ':
+ return Optional.of('r');
+ case 'Ŗ':
+ return Optional.of('r');
+ case 'ŗ':
+ return Optional.of('r');
+ case 'Ř':
+ return Optional.of('r');
+ case 'ř':
+ return Optional.of('r');
+ case 'Ś':
+ return Optional.of('s');
+ case 'ś':
+ return Optional.of('s');
+ case 'Ŝ':
+ return Optional.of('s');
+ case 'ŝ':
+ return Optional.of('s');
+ case 'Ş':
+ return Optional.of('s');
+ case 'ş':
+ return Optional.of('s');
+ case 'Š':
+ return Optional.of('s');
+ case 'š':
+ return Optional.of('s');
+ case 'Ţ':
+ return Optional.of('t');
+ case 'ţ':
+ return Optional.of('t');
+ case 'Ť':
+ return Optional.of('t');
+ case 'ť':
+ return Optional.of('t');
+ case 'Ŧ':
+ return Optional.of('t');
+ case 'ŧ':
+ return Optional.of('t');
+ case 'Ũ':
+ return Optional.of('u');
+ case 'ũ':
+ return Optional.of('u');
+ case 'Ū':
+ return Optional.of('u');
+ case 'ū':
+ return Optional.of('u');
+ case 'Ŭ':
+ return Optional.of('u');
+ case 'ŭ':
+ return Optional.of('u');
+ case 'Ů':
+ return Optional.of('u');
+ case 'ů':
+ return Optional.of('u');
+ case 'Ű':
+ return Optional.of('u');
+ case 'ű':
+ return Optional.of('u');
+ case 'Ų':
+ return Optional.of('u');
+ case 'ų':
+ return Optional.of('u');
+ case 'Ŵ':
+ return Optional.of('w');
+ case 'ŵ':
+ return Optional.of('w');
+ case 'Ŷ':
+ return Optional.of('y');
+ case 'ŷ':
+ return Optional.of('y');
+ case 'Ÿ':
+ return Optional.of('y');
+ case 'Ź':
+ return Optional.of('z');
+ case 'ź':
+ return Optional.of('z');
+ case 'Ż':
+ return Optional.of('z');
+ case 'ż':
+ return Optional.of('z');
+ case 'Ž':
+ return Optional.of('z');
+ case 'ž':
+ return Optional.of('z');
+ case 'ſ':
+ return Optional.of('s');
+ case 'ƀ':
+ return Optional.of('b');
+ case 'Ɓ':
+ return Optional.of('b');
+ case 'Ƃ':
+ return Optional.of('b');
+ case 'ƃ':
+ return Optional.of('b');
+ case 'Ɔ':
+ return Optional.of('o');
+ case 'Ƈ':
+ return Optional.of('c');
+ case 'ƈ':
+ return Optional.of('c');
+ case 'Ɖ':
+ return Optional.of('d');
+ case 'Ɗ':
+ return Optional.of('d');
+ case 'Ƌ':
+ return Optional.of('d');
+ case 'ƌ':
+ return Optional.of('d');
+ case 'ƍ':
+ return Optional.of('d');
+ case 'Ɛ':
+ return Optional.of('e');
+ case 'Ƒ':
+ return Optional.of('f');
+ case 'ƒ':
+ return Optional.of('f');
+ case 'Ɠ':
+ return Optional.of('g');
+ case 'Ɣ':
+ return Optional.of('g');
+ case 'Ɩ':
+ return Optional.of('i');
+ case 'Ɨ':
+ return Optional.of('i');
+ case 'Ƙ':
+ return Optional.of('k');
+ case 'ƙ':
+ return Optional.of('k');
+ case 'ƚ':
+ return Optional.of('l');
+ case 'ƛ':
+ return Optional.of('l');
+ case 'Ɯ':
+ return Optional.of('w');
+ case 'Ɲ':
+ return Optional.of('n');
+ case 'ƞ':
+ return Optional.of('n');
+ case 'Ɵ':
+ return Optional.of('o');
+ case 'Ơ':
+ return Optional.of('o');
+ case 'ơ':
+ return Optional.of('o');
+ case 'Ƥ':
+ return Optional.of('p');
+ case 'ƥ':
+ return Optional.of('p');
+ case 'ƫ':
+ return Optional.of('t');
+ case 'Ƭ':
+ return Optional.of('t');
+ case 'ƭ':
+ return Optional.of('t');
+ case 'Ʈ':
+ return Optional.of('t');
+ case 'Ư':
+ return Optional.of('u');
+ case 'ư':
+ return Optional.of('u');
+ case 'Ʊ':
+ return Optional.of('y');
+ case 'Ʋ':
+ return Optional.of('v');
+ case 'Ƴ':
+ return Optional.of('y');
+ case 'ƴ':
+ return Optional.of('y');
+ case 'Ƶ':
+ return Optional.of('z');
+ case 'ƶ':
+ return Optional.of('z');
+ case 'ƿ':
+ return Optional.of('w');
+ case 'Ǎ':
+ return Optional.of('a');
+ case 'ǎ':
+ return Optional.of('a');
+ case 'Ǐ':
+ return Optional.of('i');
+ case 'ǐ':
+ return Optional.of('i');
+ case 'Ǒ':
+ return Optional.of('o');
+ case 'ǒ':
+ return Optional.of('o');
+ case 'Ǔ':
+ return Optional.of('u');
+ case 'ǔ':
+ return Optional.of('u');
+ case 'Ǖ':
+ return Optional.of('u');
+ case 'ǖ':
+ return Optional.of('u');
+ case 'Ǘ':
+ return Optional.of('u');
+ case 'ǘ':
+ return Optional.of('u');
+ case 'Ǚ':
+ return Optional.of('u');
+ case 'ǚ':
+ return Optional.of('u');
+ case 'Ǜ':
+ return Optional.of('u');
+ case 'ǜ':
+ return Optional.of('u');
+ case 'Ǟ':
+ return Optional.of('a');
+ case 'ǟ':
+ return Optional.of('a');
+ case 'Ǡ':
+ return Optional.of('a');
+ case 'ǡ':
+ return Optional.of('a');
+ case 'Ǥ':
+ return Optional.of('g');
+ case 'ǥ':
+ return Optional.of('g');
+ case 'Ǧ':
+ return Optional.of('g');
+ case 'ǧ':
+ return Optional.of('g');
+ case 'Ǩ':
+ return Optional.of('k');
+ case 'ǩ':
+ return Optional.of('k');
+ case 'Ǫ':
+ return Optional.of('o');
+ case 'ǫ':
+ return Optional.of('o');
+ case 'Ǭ':
+ return Optional.of('o');
+ case 'ǭ':
+ return Optional.of('o');
+ case 'ǰ':
+ return Optional.of('j');
+ case 'Dz':
+ return Optional.of('d');
+ case 'Ǵ':
+ return Optional.of('g');
+ case 'ǵ':
+ return Optional.of('g');
+ case 'Ƿ':
+ return Optional.of('w');
+ case 'Ǹ':
+ return Optional.of('n');
+ case 'ǹ':
+ return Optional.of('n');
+ case 'Ǻ':
+ return Optional.of('a');
+ case 'ǻ':
+ return Optional.of('a');
+ case 'Ǿ':
+ return Optional.of('o');
+ case 'ǿ':
+ return Optional.of('o');
+ case 'Ȁ':
+ return Optional.of('a');
+ case 'ȁ':
+ return Optional.of('a');
+ case 'Ȃ':
+ return Optional.of('a');
+ case 'ȃ':
+ return Optional.of('a');
+ case 'Ȅ':
+ return Optional.of('e');
+ case 'ȅ':
+ return Optional.of('e');
+ case 'Ȇ':
+ return Optional.of('e');
+ case 'ȇ':
+ return Optional.of('e');
+ case 'Ȉ':
+ return Optional.of('i');
+ case 'ȉ':
+ return Optional.of('i');
+ case 'Ȋ':
+ return Optional.of('i');
+ case 'ȋ':
+ return Optional.of('i');
+ case 'Ȍ':
+ return Optional.of('o');
+ case 'ȍ':
+ return Optional.of('o');
+ case 'Ȏ':
+ return Optional.of('o');
+ case 'ȏ':
+ return Optional.of('o');
+ case 'Ȑ':
+ return Optional.of('r');
+ case 'ȑ':
+ return Optional.of('r');
+ case 'Ȓ':
+ return Optional.of('r');
+ case 'ȓ':
+ return Optional.of('r');
+ case 'Ȕ':
+ return Optional.of('u');
+ case 'ȕ':
+ return Optional.of('u');
+ case 'Ȗ':
+ return Optional.of('u');
+ case 'ȗ':
+ return Optional.of('u');
+ case 'Ș':
+ return Optional.of('s');
+ case 'ș':
+ return Optional.of('s');
+ case 'Ț':
+ return Optional.of('t');
+ case 'ț':
+ return Optional.of('t');
+ case 'Ȝ':
+ return Optional.of('y');
+ case 'ȝ':
+ return Optional.of('y');
+ case 'Ȟ':
+ return Optional.of('h');
+ case 'ȟ':
+ return Optional.of('h');
+ case 'Ȥ':
+ return Optional.of('z');
+ case 'ȥ':
+ return Optional.of('z');
+ case 'Ȧ':
+ return Optional.of('a');
+ case 'ȧ':
+ return Optional.of('a');
+ case 'Ȩ':
+ return Optional.of('e');
+ case 'ȩ':
+ return Optional.of('e');
+ case 'Ȫ':
+ return Optional.of('o');
+ case 'ȫ':
+ return Optional.of('o');
+ case 'Ȭ':
+ return Optional.of('o');
+ case 'ȭ':
+ return Optional.of('o');
+ case 'Ȯ':
+ return Optional.of('o');
+ case 'ȯ':
+ return Optional.of('o');
+ case 'Ȱ':
+ return Optional.of('o');
+ case 'ȱ':
+ return Optional.of('o');
+ case 'Ȳ':
+ return Optional.of('y');
+ case 'ȳ':
+ return Optional.of('y');
+ case 'A':
+ return Optional.of('a');
+ case 'B':
+ return Optional.of('b');
+ case 'C':
+ return Optional.of('c');
+ case 'D':
+ return Optional.of('d');
+ case 'E':
+ return Optional.of('e');
+ case 'F':
+ return Optional.of('f');
+ case 'G':
+ return Optional.of('g');
+ case 'H':
+ return Optional.of('h');
+ case 'I':
+ return Optional.of('i');
+ case 'J':
+ return Optional.of('j');
+ case 'K':
+ return Optional.of('k');
+ case 'L':
+ return Optional.of('l');
+ case 'M':
+ return Optional.of('m');
+ case 'N':
+ return Optional.of('n');
+ case 'O':
+ return Optional.of('o');
+ case 'P':
+ return Optional.of('p');
+ case 'Q':
+ return Optional.of('q');
+ case 'R':
+ return Optional.of('r');
+ case 'S':
+ return Optional.of('s');
+ case 'T':
+ return Optional.of('t');
+ case 'U':
+ return Optional.of('u');
+ case 'V':
+ return Optional.of('v');
+ case 'W':
+ return Optional.of('w');
+ case 'X':
+ return Optional.of('x');
+ case 'Y':
+ return Optional.of('y');
+ case 'Z':
+ return Optional.of('z');
+ default:
+ return Optional.absent();
+ }
+ }
+
+ @Override
+ SimpleArrayMap<Character, Character> getCharToKeyMap() {
+ return CHAR_TO_KEY_MAP;
+ }
+}
diff --git a/java/com/android/dialer/smartdial/map/RussianSmartDialMap.java b/java/com/android/dialer/smartdial/map/RussianSmartDialMap.java
new file mode 100644
index 000000000..5038520c2
--- /dev/null
+++ b/java/com/android/dialer/smartdial/map/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.map;
+
+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<Character, Character> 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<Character> normalizeCharacter(char ch) {
+ ch = Character.toLowerCase(ch);
+ return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent();
+ }
+
+ @Override
+ SimpleArrayMap<Character, Character> getCharToKeyMap() {
+ return CHAR_TO_KEY_MAP;
+ }
+}
diff --git a/java/com/android/dialer/smartdial/map/SmartDialMap.java b/java/com/android/dialer/smartdial/map/SmartDialMap.java
new file mode 100644
index 000000000..c74dd2893
--- /dev/null
+++ b/java/com/android/dialer/smartdial/map/SmartDialMap.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 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.map;
+
+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.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ */
+ 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.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ */
+ 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.
+ */
+ protected boolean isValidDialpadNumericChar(char ch) {
+ return '0' <= ch && ch <= '9';
+ }
+
+ /**
+ * Get the index of the key on the dialpad which the character corresponds to.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ *
+ * <p>An {@link Optional#absent()} is returned if the provided character can't be mapped to a key
+ * on the dialpad.
+ */
+ protected Optional<Byte> getDialpadIndex(char ch) {
+ if (isValidDialpadNumericChar(ch)) {
+ return Optional.of((byte) (ch - '0'));
+ }
+
+ 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.
+ *
+ * <p>The provided character is expected to be a normalized character. See {@link
+ * SmartDialMap#normalizeCharacter(char)} for details.
+ *
+ * <p>An {@link Optional#absent()} is returned if the provided character can't be mapped to a key
+ * on the dialpad.
+ */
+ protected Optional<Character> 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.
+ *
+ * <p>An {@link Optional#absent()} is returned if the provided character can't be mapped to a key
+ * on the dialpad.
+ */
+ abstract Optional<Character> normalizeCharacter(char ch);
+
+ /**
+ * Returns a map in which each key is a normalized character and the corresponding value is a
+ * dialpad key.
+ */
+ abstract SimpleArrayMap<Character, Character> getCharToKeyMap();
+}
diff --git a/java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java b/java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java
new file mode 100644
index 000000000..28dbc0d61
--- /dev/null
+++ b/java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java
@@ -0,0 +1,93 @@
+/*
+ * 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.map;
+
+import android.support.v4.util.SimpleArrayMap;
+import com.google.common.base.Optional;
+
+/** A {@link SmartDialMap} for the Ukrainian alphabet. */
+final class UkrainianSmartDialMap extends SmartDialMap {
+ private static final SimpleArrayMap<Character, Character> CHAR_TO_KEY_MAP =
+ new SimpleArrayMap<>();
+
+ // Reference: https://en.wikipedia.org/wiki/Ukrainian_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('ґ', '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('к', '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('ь', '9');
+ CHAR_TO_KEY_MAP.put('ю', '9');
+ CHAR_TO_KEY_MAP.put('я', '9');
+ }
+
+ private static UkrainianSmartDialMap instance;
+
+ static UkrainianSmartDialMap getInstance() {
+ if (instance == null) {
+ instance = new UkrainianSmartDialMap();
+ }
+
+ return instance;
+ }
+
+ private UkrainianSmartDialMap() {}
+
+ @Override
+ Optional<Character> normalizeCharacter(char ch) {
+ ch = Character.toLowerCase(ch);
+ return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent();
+ }
+
+ @Override
+ SimpleArrayMap<Character, Character> getCharToKeyMap() {
+ return CHAR_TO_KEY_MAP;
+ }
+}