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 98 @UnsupportedAppUsage CdmaSmsAddress()99 public CdmaSmsAddress(){ 100 } 101 102 @Override toString()103 public String toString() { 104 StringBuilder builder = new StringBuilder(); 105 builder.append("CdmaSmsAddress "); 106 builder.append("{ digitMode=" + digitMode); 107 builder.append(", numberMode=" + numberMode); 108 builder.append(", numberPlan=" + numberPlan); 109 builder.append(", numberOfDigits=" + numberOfDigits); 110 builder.append(", ton=" + ton); 111 builder.append(", address=\"" + address + "\""); 112 builder.append(", origBytes=" + HexDump.toHexString(origBytes)); 113 builder.append(" }"); 114 return builder.toString(); 115 } 116 117 /* 118 * TODO(cleanup): Refactor the parsing for addresses to better 119 * share code and logic with GSM. Also, gather all DTMF/BCD 120 * processing code in one place. 121 */ 122 @VisibleForTesting parseToDtmf(String address)123 public static byte[] parseToDtmf(String address) { 124 int digits = address.length(); 125 byte[] result = new byte[digits]; 126 for (int i = 0; i < digits; i++) { 127 char c = address.charAt(i); 128 int val = 0; 129 if ((c >= '1') && (c <= '9')) val = c - '0'; 130 else if (c == '0') val = 10; 131 else if (c == '*') val = 11; 132 else if (c == '#') val = 12; 133 else return null; 134 result[i] = (byte)val; 135 } 136 return result; 137 } 138 139 private static final char[] numericCharsDialable = { 140 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#' 141 }; 142 143 private static final char[] numericCharsSugar = { 144 '(', ')', ' ', '-', '+', '.', '/', '\\' 145 }; 146 147 private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray ( 148 numericCharsDialable.length + numericCharsSugar.length); 149 static { 150 for (int i = 0; i < numericCharsDialable.length; i++) { numericCharDialableMap.put(numericCharsDialable[i], true)151 numericCharDialableMap.put(numericCharsDialable[i], true); 152 } 153 for (int i = 0; i < numericCharsSugar.length; i++) { numericCharDialableMap.put(numericCharsSugar[i], false)154 numericCharDialableMap.put(numericCharsSugar[i], false); 155 } 156 } 157 158 /** 159 * Given a numeric address string, return the string without 160 * syntactic sugar, meaning parens, spaces, hyphens/minuses, or 161 * plus signs. If the input string contains non-numeric 162 * non-punctuation characters, return null. 163 */ filterNumericSugar(String address)164 private static String filterNumericSugar(String address) { 165 StringBuilder builder = new StringBuilder(); 166 int len = address.length(); 167 for (int i = 0; i < len; i++) { 168 char c = address.charAt(i); 169 int mapIndex = numericCharDialableMap.indexOfKey(c); 170 if (mapIndex < 0) return null; 171 if (! numericCharDialableMap.valueAt(mapIndex)) continue; 172 builder.append(c); 173 } 174 return builder.toString(); 175 } 176 177 /** 178 * Given a string, return the string without whitespace, 179 * including CR/LF. 180 */ filterWhitespace(String address)181 private static String filterWhitespace(String address) { 182 StringBuilder builder = new StringBuilder(); 183 int len = address.length(); 184 for (int i = 0; i < len; i++) { 185 char c = address.charAt(i); 186 if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue; 187 builder.append(c); 188 } 189 return builder.toString(); 190 } 191 192 /** 193 * Given a string, create a corresponding CdmaSmsAddress object. 194 * 195 * The result will be null if the input string is not 196 * representable using printable ASCII. 197 * 198 * For numeric addresses, the string is cleaned up by removing 199 * common punctuation. For alpha addresses, the string is cleaned 200 * up by removing whitespace. 201 */ 202 @UnsupportedAppUsage parse(String address)203 public static CdmaSmsAddress parse(String address) { 204 CdmaSmsAddress addr = new CdmaSmsAddress(); 205 addr.address = address; 206 addr.ton = TON_UNKNOWN; 207 addr.digitMode = DIGIT_MODE_4BIT_DTMF; 208 addr.numberPlan = NUMBERING_PLAN_UNKNOWN; 209 addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; 210 211 byte[] origBytes; 212 String filteredAddr = filterNumericSugar(address); 213 if (address.contains("+") || filteredAddr == null) { 214 // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters 215 // NUMBER_MODE should set to 1 for network address and email address. 216 addr.digitMode = DIGIT_MODE_8BIT_CHAR; 217 addr.numberMode = NUMBER_MODE_DATA_NETWORK; 218 filteredAddr = filterWhitespace(address); 219 220 if (address.contains("@")) { 221 // This is an email address 222 addr.ton = TON_NATIONAL_OR_EMAIL; 223 } else if (address.contains("+") && filterNumericSugar(address) != null) { 224 // This is an international number 225 // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters 226 // digit mode is set to 1 and number mode is set to 0, type of number should set 227 // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2 228 addr.ton = TON_INTERNATIONAL_OR_IP; 229 addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY; 230 addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK; 231 filteredAddr = filterNumericSugar(address); 232 } 233 234 origBytes = UserData.stringToAscii(filteredAddr); 235 } else { 236 // The address is not an international number and it only contains digit and *# 237 origBytes = parseToDtmf(filteredAddr); 238 } 239 240 if (origBytes == null) { 241 return null; 242 } 243 244 addr.origBytes = origBytes; 245 addr.numberOfDigits = origBytes.length; 246 return addr; 247 } 248 } 249