Home | History | Annotate | Download | only in telephony
      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
     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      */
    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      */
    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      */
    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      */
    142     public String getMessageBody() {
    143         return mMessageBody;
    144     }
    145 
    146     /**
    147      * Returns the class of this message.
    148      */
    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      */
    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      */
    167     public String getPseudoSubject() {
    168         return mPseudoSubject == null ? "" : mPseudoSubject;
    169     }
    170 
    171     /**
    172      * Returns the service centre timestamp in currentTimeMillis() format
    173      */
    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      */
    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      */
    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      */
    200     public String getEmailFrom() {
    201         return mEmailFrom;
    202     }
    203 
    204     /**
    205      * Get protocol identifier.
    206      */
    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      */
    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      */
    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      */
    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      */
    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      */
    239     public abstract boolean isMwiDontStore();
    240 
    241     /**
    242      * returns the user data section minus the user data header if one was
    243      * present.
    244      */
    245     public byte[] getUserData() {
    246         return mUserData;
    247     }
    248 
    249     /**
    250      * Returns an object representing the user data header
    251      *
    252      * {@hide}
    253      */
    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      */
    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      */
    282     public abstract int getStatus();
    283 
    284     /**
    285      * Return true iff the message is a SMS-STATUS-REPORT message.
    286      */
    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      */
    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      */
    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      */
    314     public int getIndexOnIcc() {
    315         return mIndexOnIcc;
    316     }
    317 
    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      */
    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      */
    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      */
    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