1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dialer.smartdial.map; 18 19 import android.content.Context; 20 import android.support.annotation.VisibleForTesting; 21 import android.support.v4.util.SimpleArrayMap; 22 import com.android.dialer.i18n.LocaleUtils; 23 import com.google.common.base.Optional; 24 25 /** 26 * A utility class that combines the functionality of two implementations of {@link SmartDialMap} so 27 * that we support smart dial for dual alphabets. 28 * 29 * <p>Of the two implementations of {@link SmartDialMap}, the default one always takes precedence. 30 * The second one is consulted only when the default one is unable to provide a valid result. 31 * 32 * <p>Note that the second implementation can be absent if it is not defined for the system's 1st 33 * language preference. 34 */ 35 @SuppressWarnings("Guava") 36 public class CompositeSmartDialMap { 37 38 private static final SmartDialMap DEFAULT_MAP = LatinSmartDialMap.getInstance(); 39 40 // A map in which each key is an ISO 639-2 language code and the corresponding value is a 41 // SmartDialMap 42 private static final SimpleArrayMap<String, SmartDialMap> EXTRA_MAPS = new SimpleArrayMap<>(); 43 44 static { 45 EXTRA_MAPS.put("bul", BulgarianSmartDialMap.getInstance()); 46 EXTRA_MAPS.put("rus", RussianSmartDialMap.getInstance()); 47 EXTRA_MAPS.put("ukr", UkrainianSmartDialMap.getInstance()); 48 } 49 CompositeSmartDialMap()50 private CompositeSmartDialMap() {} 51 52 /** 53 * Returns true if the provided character can be mapped to a key on the dialpad. 54 * 55 * <p>The provided character is expected to be a normalized character. See {@link 56 * SmartDialMap#normalizeCharacter(char)} for details. 57 */ isValidDialpadCharacter(Context context, char ch)58 public static boolean isValidDialpadCharacter(Context context, char ch) { 59 if (DEFAULT_MAP.isValidDialpadCharacter(ch)) { 60 return true; 61 } 62 63 Optional<SmartDialMap> extraMap = getExtraMap(context); 64 return extraMap.isPresent() && extraMap.get().isValidDialpadCharacter(ch); 65 } 66 67 /** 68 * Returns true if the provided character is a letter, and can be mapped to a key on the dialpad. 69 * 70 * <p>The provided character is expected to be a normalized character. See {@link 71 * SmartDialMap#normalizeCharacter(char)} for details. 72 */ isValidDialpadAlphabeticChar(Context context, char ch)73 public static boolean isValidDialpadAlphabeticChar(Context context, char ch) { 74 if (DEFAULT_MAP.isValidDialpadAlphabeticChar(ch)) { 75 return true; 76 } 77 78 Optional<SmartDialMap> extraMap = getExtraMap(context); 79 return extraMap.isPresent() && extraMap.get().isValidDialpadAlphabeticChar(ch); 80 } 81 82 /** 83 * Returns true if the provided character is a digit, and can be mapped to a key on the dialpad. 84 */ isValidDialpadNumericChar(Context context, char ch)85 public static boolean isValidDialpadNumericChar(Context context, char ch) { 86 if (DEFAULT_MAP.isValidDialpadNumericChar(ch)) { 87 return true; 88 } 89 90 Optional<SmartDialMap> extraMap = getExtraMap(context); 91 return extraMap.isPresent() && extraMap.get().isValidDialpadNumericChar(ch); 92 } 93 94 /** 95 * Get the index of the key on the dialpad which the character corresponds to. 96 * 97 * <p>The provided character is expected to be a normalized character. See {@link 98 * SmartDialMap#normalizeCharacter(char)} for details. 99 * 100 * <p>If the provided character can't be mapped to a key on the dialpad, return -1. 101 */ getDialpadIndex(Context context, char ch)102 public static byte getDialpadIndex(Context context, char ch) { 103 Optional<Byte> dialpadIndex = DEFAULT_MAP.getDialpadIndex(ch); 104 if (dialpadIndex.isPresent()) { 105 return dialpadIndex.get(); 106 } 107 108 Optional<SmartDialMap> extraMap = getExtraMap(context); 109 if (extraMap.isPresent()) { 110 dialpadIndex = extraMap.get().getDialpadIndex(ch); 111 } 112 113 return dialpadIndex.isPresent() ? dialpadIndex.get() : -1; 114 } 115 116 /** 117 * Get the actual numeric character on the dialpad which the character corresponds to. 118 * 119 * <p>The provided character is expected to be a normalized character. See {@link 120 * SmartDialMap#normalizeCharacter(char)} for details. 121 * 122 * <p>If the provided character can't be mapped to a key on the dialpad, return the character. 123 */ getDialpadNumericCharacter(Context context, char ch)124 public static char getDialpadNumericCharacter(Context context, char ch) { 125 Optional<Character> dialpadNumericChar = DEFAULT_MAP.getDialpadNumericCharacter(ch); 126 if (dialpadNumericChar.isPresent()) { 127 return dialpadNumericChar.get(); 128 } 129 130 Optional<SmartDialMap> extraMap = getExtraMap(context); 131 if (extraMap.isPresent()) { 132 dialpadNumericChar = extraMap.get().getDialpadNumericCharacter(ch); 133 } 134 135 return dialpadNumericChar.isPresent() ? dialpadNumericChar.get() : ch; 136 } 137 138 /** 139 * Converts uppercase characters to lower case ones, and on a best effort basis, strips accents 140 * from accented characters. 141 * 142 * <p>If the provided character can't be mapped to a key on the dialpad, return the character. 143 */ normalizeCharacter(Context context, char ch)144 public static char normalizeCharacter(Context context, char ch) { 145 Optional<Character> normalizedChar = DEFAULT_MAP.normalizeCharacter(ch); 146 if (normalizedChar.isPresent()) { 147 return normalizedChar.get(); 148 } 149 150 Optional<SmartDialMap> extraMap = getExtraMap(context); 151 if (extraMap.isPresent()) { 152 normalizedChar = extraMap.get().normalizeCharacter(ch); 153 } 154 155 return normalizedChar.isPresent() ? normalizedChar.get() : ch; 156 } 157 158 @VisibleForTesting getExtraMap(Context context)159 static Optional<SmartDialMap> getExtraMap(Context context) { 160 String languageCode = LocaleUtils.getLocale(context).getISO3Language(); 161 return EXTRA_MAPS.containsKey(languageCode) 162 ? Optional.of(EXTRA_MAPS.get(languageCode)) 163 : Optional.absent(); 164 } 165 } 166