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