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 17package com.android.internal.telephony; 18 19import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 20import com.android.internal.telephony.SmsConstants; 21import com.android.internal.telephony.SmsHeader; 22import java.text.BreakIterator; 23import java.util.Arrays; 24 25import android.provider.Telephony; 26import android.telephony.SmsMessage; 27 28/** 29 * Base class declaring the specific methods and members for SmsMessage. 30 * {@hide} 31 */ 32public abstract class SmsMessageBase { 33 /** {@hide} The address of the SMSC. May be null */ 34 protected String mScAddress; 35 36 /** {@hide} The address of the sender */ 37 protected SmsAddress mOriginatingAddress; 38 39 /** {@hide} The message body as a string. May be null if the message isn't text */ 40 protected String mMessageBody; 41 42 /** {@hide} */ 43 protected String mPseudoSubject; 44 45 /** {@hide} Non-null if this is an email gateway message */ 46 protected String mEmailFrom; 47 48 /** {@hide} Non-null if this is an email gateway message */ 49 protected String mEmailBody; 50 51 /** {@hide} */ 52 protected boolean mIsEmail; 53 54 /** {@hide} */ 55 protected long mScTimeMillis; 56 57 /** {@hide} The raw PDU of the message */ 58 protected byte[] mPdu; 59 60 /** {@hide} The raw bytes for the user data section of the message */ 61 protected byte[] mUserData; 62 63 /** {@hide} */ 64 protected SmsHeader mUserDataHeader; 65 66 // "Message Waiting Indication Group" 67 // 23.038 Section 4 68 /** {@hide} */ 69 protected boolean mIsMwi; 70 71 /** {@hide} */ 72 protected boolean mMwiSense; 73 74 /** {@hide} */ 75 protected boolean mMwiDontStore; 76 77 /** 78 * Indicates status for messages stored on the ICC. 79 */ 80 protected int mStatusOnIcc = -1; 81 82 /** 83 * Record index of message in the EF. 84 */ 85 protected int mIndexOnIcc = -1; 86 87 /** TP-Message-Reference - Message Reference of sent message. @hide */ 88 public int mMessageRef; 89 90 // TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly. 91 public static abstract class SubmitPduBase { 92 public byte[] encodedScAddress; // Null if not applicable. 93 public byte[] encodedMessage; 94 95 @Override 96 public String toString() { 97 return "SubmitPdu: encodedScAddress = " 98 + Arrays.toString(encodedScAddress) 99 + ", encodedMessage = " 100 + Arrays.toString(encodedMessage); 101 } 102 } 103 104 /** 105 * Returns the address of the SMS service center that relayed this message 106 * or null if there is none. 107 */ 108 public String getServiceCenterAddress() { 109 return mScAddress; 110 } 111 112 /** 113 * Returns the originating address (sender) of this SMS message in String 114 * form or null if unavailable 115 */ 116 public String getOriginatingAddress() { 117 if (mOriginatingAddress == null) { 118 return null; 119 } 120 121 return mOriginatingAddress.getAddressString(); 122 } 123 124 /** 125 * Returns the originating address, or email from address if this message 126 * was from an email gateway. Returns null if originating address 127 * unavailable. 128 */ 129 public String getDisplayOriginatingAddress() { 130 if (mIsEmail) { 131 return mEmailFrom; 132 } else { 133 return getOriginatingAddress(); 134 } 135 } 136 137 /** 138 * Returns the message body as a String, if it exists and is text based. 139 * @return message body is there is one, otherwise null 140 */ 141 public String getMessageBody() { 142 return mMessageBody; 143 } 144 145 /** 146 * Returns the class of this message. 147 */ 148 public abstract SmsConstants.MessageClass getMessageClass(); 149 150 /** 151 * Returns the message body, or email message body if this message was from 152 * an email gateway. Returns null if message body unavailable. 153 */ 154 public String getDisplayMessageBody() { 155 if (mIsEmail) { 156 return mEmailBody; 157 } else { 158 return getMessageBody(); 159 } 160 } 161 162 /** 163 * Unofficial convention of a subject line enclosed in parens empty string 164 * if not present 165 */ 166 public String getPseudoSubject() { 167 return mPseudoSubject == null ? "" : mPseudoSubject; 168 } 169 170 /** 171 * Returns the service centre timestamp in currentTimeMillis() format 172 */ 173 public long getTimestampMillis() { 174 return mScTimeMillis; 175 } 176 177 /** 178 * Returns true if message is an email. 179 * 180 * @return true if this message came through an email gateway and email 181 * sender / subject / parsed body are available 182 */ 183 public boolean isEmail() { 184 return mIsEmail; 185 } 186 187 /** 188 * @return if isEmail() is true, body of the email sent through the gateway. 189 * null otherwise 190 */ 191 public String getEmailBody() { 192 return mEmailBody; 193 } 194 195 /** 196 * @return if isEmail() is true, email from address of email sent through 197 * the gateway. null otherwise 198 */ 199 public String getEmailFrom() { 200 return mEmailFrom; 201 } 202 203 /** 204 * Get protocol identifier. 205 */ 206 public abstract int getProtocolIdentifier(); 207 208 /** 209 * See TS 23.040 9.2.3.9 returns true if this is a "replace short message" 210 * SMS 211 */ 212 public abstract boolean isReplace(); 213 214 /** 215 * Returns true for CPHS MWI toggle message. 216 * 217 * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section 218 * B.4.2 219 */ 220 public abstract boolean isCphsMwiMessage(); 221 222 /** 223 * returns true if this message is a CPHS voicemail / message waiting 224 * indicator (MWI) clear message 225 */ 226 public abstract boolean isMWIClearMessage(); 227 228 /** 229 * returns true if this message is a CPHS voicemail / message waiting 230 * indicator (MWI) set message 231 */ 232 public abstract boolean isMWISetMessage(); 233 234 /** 235 * returns true if this message is a "Message Waiting Indication Group: 236 * Discard Message" notification and should not be stored. 237 */ 238 public abstract boolean isMwiDontStore(); 239 240 /** 241 * returns the user data section minus the user data header if one was 242 * present. 243 */ 244 public byte[] getUserData() { 245 return mUserData; 246 } 247 248 /** 249 * Returns an object representing the user data header 250 * 251 * {@hide} 252 */ 253 public SmsHeader getUserDataHeader() { 254 return mUserDataHeader; 255 } 256 257 /** 258 * TODO(cleanup): The term PDU is used in a seemingly non-unique 259 * manner -- for example, what is the difference between this byte 260 * array and the contents of SubmitPdu objects. Maybe a more 261 * illustrative term would be appropriate. 262 */ 263 264 /** 265 * Returns the raw PDU for the message. 266 */ 267 public byte[] getPdu() { 268 return mPdu; 269 } 270 271 /** 272 * For an SMS-STATUS-REPORT message, this returns the status field from 273 * the status report. This field indicates the status of a previously 274 * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a 275 * description of values. 276 * 277 * @return 0 indicates the previously sent message was received. 278 * See TS 23.040, 9.9.2.3.15 for a description of other possible 279 * values. 280 */ 281 public abstract int getStatus(); 282 283 /** 284 * Return true iff the message is a SMS-STATUS-REPORT message. 285 */ 286 public abstract boolean isStatusReportMessage(); 287 288 /** 289 * Returns true iff the <code>TP-Reply-Path</code> bit is set in 290 * this message. 291 */ 292 public abstract boolean isReplyPathPresent(); 293 294 /** 295 * Returns the status of the message on the ICC (read, unread, sent, unsent). 296 * 297 * @return the status of the message on the ICC. These are: 298 * SmsManager.STATUS_ON_ICC_FREE 299 * SmsManager.STATUS_ON_ICC_READ 300 * SmsManager.STATUS_ON_ICC_UNREAD 301 * SmsManager.STATUS_ON_ICC_SEND 302 * SmsManager.STATUS_ON_ICC_UNSENT 303 */ 304 public int getStatusOnIcc() { 305 return mStatusOnIcc; 306 } 307 308 /** 309 * Returns the record index of the message on the ICC (1-based index). 310 * @return the record index of the message on the ICC, or -1 if this 311 * SmsMessage was not created from a ICC SMS EF record. 312 */ 313 public int getIndexOnIcc() { 314 return mIndexOnIcc; 315 } 316 317 protected void parseMessageBody() { 318 // originatingAddress could be null if this message is from a status 319 // report. 320 if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) { 321 extractEmailAddressFromMessageBody(); 322 } 323 } 324 325 /** 326 * Try to parse this message as an email gateway message 327 * There are two ways specified in TS 23.040 Section 3.8 : 328 * - SMS message "may have its TP-PID set for Internet electronic mail - MT 329 * SMS format: [<from-address><space>]<message> - "Depending on the 330 * nature of the gateway, the destination/origination address is either 331 * derived from the content of the SMS TP-OA or TP-DA field, or the 332 * TP-OA/TP-DA field contains a generic gateway address and the to/from 333 * address is added at the beginning as shown above." (which is supported here) 334 * - Multiple addresses separated by commas, no spaces, Subject field delimited 335 * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here) 336 */ 337 protected void extractEmailAddressFromMessageBody() { 338 339 /* Some carriers may use " /" delimiter as below 340 * 341 * 1. [x@y][ ]/[subject][ ]/[body] 342 * -or- 343 * 2. [x@y][ ]/[body] 344 */ 345 String[] parts = mMessageBody.split("( /)|( )", 2); 346 if (parts.length < 2) return; 347 mEmailFrom = parts[0]; 348 mEmailBody = parts[1]; 349 mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom); 350 } 351 352 /** 353 * Find the next position to start a new fragment of a multipart SMS. 354 * 355 * @param currentPosition current start position of the fragment 356 * @param byteLimit maximum number of bytes in the fragment 357 * @param msgBody text of the SMS in UTF-16 encoding 358 * @return the position to start the next fragment 359 */ 360 public static int findNextUnicodePosition( 361 int currentPosition, int byteLimit, CharSequence msgBody) { 362 int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length()); 363 // Check whether the fragment ends in a character boundary. Some characters take 4-bytes 364 // in UTF-16 encoding. Many carriers cannot handle 365 // a fragment correctly if it does not end at a character boundary. 366 if (nextPos < msgBody.length()) { 367 BreakIterator breakIterator = BreakIterator.getCharacterInstance(); 368 breakIterator.setText(msgBody.toString()); 369 if (!breakIterator.isBoundary(nextPos)) { 370 nextPos = breakIterator.preceding(nextPos); 371 } 372 } 373 return nextPos; 374 } 375 376 /** 377 * Calculate the TextEncodingDetails of a message encoded in Unicode. 378 */ 379 public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) { 380 TextEncodingDetails ted = new TextEncodingDetails(); 381 int octets = msgBody.length() * 2; 382 ted.codeUnitSize = SmsConstants.ENCODING_16BIT; 383 ted.codeUnitCount = msgBody.length(); 384 if (octets > SmsConstants.MAX_USER_DATA_BYTES) { 385 // If EMS is not supported, break down EMS into single segment SMS 386 // and add page info " x/y". 387 // In the case of UCS2 encoding type, we need 8 bytes for this 388 // but we only have 6 bytes from UDH, so truncate the limit for 389 // each segment by 2 bytes (1 char). 390 int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER; 391 if (!SmsMessage.hasEmsSupport()) { 392 // make sure total number of segments is less than 10 393 if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) { 394 maxUserDataBytesWithHeader -= 2; 395 } 396 } 397 398 int pos = 0; // Index in code units. 399 int msgCount = 0; 400 while (pos < msgBody.length()) { 401 int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader, 402 msgBody); 403 if (nextPos == msgBody.length()) { 404 ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 - 405 msgBody.length(); 406 } 407 pos = nextPos; 408 msgCount++; 409 } 410 ted.msgCount = msgCount; 411 } else { 412 ted.msgCount = 1; 413 ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2; 414 } 415 416 return ted; 417 } 418} 419