1 /* 2 * Copyright (C) 2008 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.internal.telephony.cdma.sms; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.util.SparseBooleanArray; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 import com.android.internal.telephony.SmsAddress; 24 import com.android.internal.util.HexDump; 25 26 public class CdmaSmsAddress extends SmsAddress { 27 28 /** 29 * Digit Mode Indicator is a 1-bit value that indicates whether 30 * the address digits are 4-bit DTMF codes or 8-bit codes. (See 31 * 3GPP2 C.S0015-B, v2, 3.4.3.3) 32 */ 33 static public final int DIGIT_MODE_4BIT_DTMF = 0x00; 34 static public final int DIGIT_MODE_8BIT_CHAR = 0x01; 35 36 @UnsupportedAppUsage 37 public int digitMode; 38 39 /** 40 * Number Mode Indicator is 1-bit value that indicates whether the 41 * address type is a data network address or not. (See 3GPP2 42 * C.S0015-B, v2, 3.4.3.3) 43 */ 44 static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00; 45 static public final int NUMBER_MODE_DATA_NETWORK = 0x01; 46 47 @UnsupportedAppUsage 48 public int numberMode; 49 50 /** 51 * Number Types for data networks. 52 * (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table) 53 * (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset) 54 * NOTE: value is stored in the parent class ton field. 55 */ 56 static public final int TON_UNKNOWN = 0x00; 57 static public final int TON_INTERNATIONAL_OR_IP = 0x01; 58 static public final int TON_NATIONAL_OR_EMAIL = 0x02; 59 static public final int TON_NETWORK = 0x03; 60 static public final int TON_SUBSCRIBER = 0x04; 61 static public final int TON_ALPHANUMERIC = 0x05; 62 static public final int TON_ABBREVIATED = 0x06; 63 static public final int TON_RESERVED = 0x07; 64 65 /** 66 * Maximum lengths for fields as defined in ril_cdma_sms.h. 67 */ 68 static public final int SMS_ADDRESS_MAX = 36; 69 static public final int SMS_SUBADDRESS_MAX = 36; 70 71 /** 72 * This field shall be set to the number of address digits 73 * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) 74 */ 75 @UnsupportedAppUsage 76 public int numberOfDigits; 77 78 /** 79 * Numbering Plan identification is a 0 or 4-bit value that 80 * indicates which numbering plan identification is set. (See 81 * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3) 82 */ 83 static public final int NUMBERING_PLAN_UNKNOWN = 0x0; 84 static public final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1; 85 //static protected final int NUMBERING_PLAN_DATA = 0x3; 86 //static protected final int NUMBERING_PLAN_TELEX = 0x4; 87 //static protected final int NUMBERING_PLAN_PRIVATE = 0x9; 88 89 @UnsupportedAppUsage 90 public int numberPlan; 91 92 /** 93 * NOTE: the parsed string address and the raw byte array values 94 * are stored in the parent class address and origBytes fields, 95 * respectively. 96 */ 97 @UnsupportedAppUsage CdmaSmsAddress()98 public CdmaSmsAddress(){ 99 } 100 101 @Override toString()102 public String toString() { 103 StringBuilder builder = new StringBuilder(); 104 builder.append("CdmaSmsAddress "); 105 builder.append("{ digitMode=" + digitMode); 106 builder.append(", numberMode=" + numberMode); 107 builder.append(", numberPlan=" + numberPlan); 108 builder.append(", numberOfDigits=" + numberOfDigits); 109 builder.append(", ton=" + ton); 110 builder.append(", address=\"" + address + "\""); 111 builder.append(", origBytes=" + HexDump.toHexString(origBytes)); 112 builder.append(" }"); 113 return builder.toString(); 114 } 115 116 /* 117 * TODO(cleanup): Refactor the parsing for addresses to better 118 * share code and logic with GSM. Also, gather all DTMF/BCD 119 * processing code in one place. 120 */ 121 @VisibleForTesting parseToDtmf(String address)122 public static byte[] parseToDtmf(String address) { 123 int digits = address.length(); 124 byte[] result = new byte[digits]; 125 for (int i = 0; i < digits; i++) { 126 char c = address.charAt(i); 127 int val = 0; 128 if ((c >= '1') && (c <= '9')) val = c - '0'; 129 else if (c == '0') val = 10; 130 else if (c == '*') val = 11; 131 else if (c == '#') val = 12; 132 else return null; 133 result[i] = (byte)val; 134 } 135 return result; 136 } 137 138 private static final char[] numericCharsDialable = { 139 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#' 140 }; 141 142 private static final char[] numericCharsSugar = { 143 '(', ')', ' ', '-', '+', '.', '/', '\\' 144 }; 145 146 private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray ( 147 numericCharsDialable.length + numericCharsSugar.length); 148 static { 149 for (int i = 0; i < numericCharsDialable.length; i++) { numericCharDialableMap.put(numericCharsDialable[i], true)150 numericCharDialableMap.put(numericCharsDialable[i], true); 151 } 152 for (int i = 0; i < numericCharsSugar.length; i++) { numericCharDialableMap.put(numericCharsSugar[i], false)153 numericCharDialableMap.put(numericCharsSugar[i], false); 154 } 155 } 156 157 /** 158 * Given a numeric address string, return the string without 159 * syntactic sugar, meaning parens, spaces, hyphens/minuses, or 160 * plus signs. If the input string contains non-numeric 161 * non-punctuation characters, return null. 162 */ filterNumericSugar(String address)163 private static String filterNumericSugar(String address) { 164 StringBuilder builder = new StringBuilder(); 165 int len = address.length(); 166 for (int i = 0; i < len; i++) { 167 char c = address.charAt(i); 168 int mapIndex = numericCharDialableMap.indexOfKey(c); 169 if (mapIndex < 0) return null; 170 if (! numericCharDialableMap.valueAt(mapIndex)) continue; 171 builder.append(c); 172 } 173 return builder.toString(); 174 } 175 176 /** 177 * Given a string, return the string without whitespace, 178 * including CR/LF. 179 */ filterWhitespace(String address)180 private static String filterWhitespace(String address) { 181 StringBuilder builder = new StringBuilder(); 182 int len = address.length(); 183 for (int i = 0; i < len; i++) { 184 char c = address.charAt(i); 185 if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue; 186 builder.append(c); 187 } 188 return builder.toString(); 189 } 190 191 /** 192 * Given a string, create a corresponding CdmaSmsAddress object. 193 * 194 * The result will be null if the input string is not 195 * representable using printable ASCII. 196 * 197 * For numeric addresses, the string is cleaned up by removing 198 * common punctuation. For alpha addresses, the string is cleaned 199 * up by removing whitespace. 200 */ 201 @UnsupportedAppUsage parse(String address)202 public static CdmaSmsAddress parse(String address) { 203 CdmaSmsAddress addr = new CdmaSmsAddress(); 204 addr.address = address; 205 addr.ton = TON_UNKNOWN; 206 addr.digitMode = DIGIT_MODE_4BIT_DTMF; 207 addr.numberPlan = NUMBERING_PLAN_UNKNOWN; 208 addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; 209 210 byte[] origBytes; 211 String filteredAddr = filterNumericSugar(address); 212 if (address.contains("+") || filteredAddr == null) { 213 // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters 214 // NUMBER_MODE should set to 1 for network address and email address. 215 addr.digitMode = DIGIT_MODE_8BIT_CHAR; 216 addr.numberMode = NUMBER_MODE_DATA_NETWORK; 217 filteredAddr = filterWhitespace(address); 218 219 if (address.contains("@")) { 220 // This is an email address 221 addr.ton = TON_NATIONAL_OR_EMAIL; 222 } else if (address.contains("+") && filterNumericSugar(address) != null) { 223 // This is an international number 224 // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters 225 // digit mode is set to 1 and number mode is set to 0, type of number should set 226 // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2 227 addr.ton = TON_INTERNATIONAL_OR_IP; 228 addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY; 229 addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; 230 filteredAddr = filterNumericSugar(address); 231 } 232 233 origBytes = UserData.stringToAscii(filteredAddr); 234 } else { 235 // The address is not an international number and it only contains digit and *# 236 origBytes = parseToDtmf(filteredAddr); 237 } 238 239 if (origBytes == null) { 240 return null; 241 } 242 243 addr.origBytes = origBytes; 244 addr.numberOfDigits = origBytes.length; 245 return addr; 246 } 247 } 248