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