diff options
Diffstat (limited to 'src/java/android/telephony/SmsMessage.java')
-rw-r--r-- | src/java/android/telephony/SmsMessage.java | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/src/java/android/telephony/SmsMessage.java b/src/java/android/telephony/SmsMessage.java new file mode 100644 index 0000000..b94609e --- /dev/null +++ b/src/java/android/telephony/SmsMessage.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.os.Parcel; +import android.util.Log; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; +import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.SmsHeader; +import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; + +import java.lang.Math; +import java.util.ArrayList; +import java.util.Arrays; + +import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; + + +/** + * A Short Message Service message. + */ +public class SmsMessage { + private static final String LOG_TAG = "SMS"; + + /** + * SMS Class enumeration. + * See TS 23.038. + * + */ + public enum MessageClass{ + UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; + } + + /** User data text encoding code unit size */ + public static final int ENCODING_UNKNOWN = 0; + public static final int ENCODING_7BIT = 1; + public static final int ENCODING_8BIT = 2; + public static final int ENCODING_16BIT = 3; + /** + * @hide This value is not defined in global standard. Only in Korea, this is used. + */ + public static final int ENCODING_KSC5601 = 4; + + /** The maximum number of payload bytes per message */ + public static final int MAX_USER_DATA_BYTES = 140; + + /** + * The maximum number of payload bytes per message if a user data header + * is present. This assumes the header only contains the + * CONCATENATED_8_BIT_REFERENCE element. + */ + public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; + + /** The maximum number of payload septets per message */ + public static final int MAX_USER_DATA_SEPTETS = 160; + + /** + * The maximum number of payload septets per message if a user data header + * is present. This assumes the header only contains the + * CONCATENATED_8_BIT_REFERENCE element. + */ + public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153; + + /** + * Indicates a 3GPP format SMS message. + * @hide pending API council approval + */ + public static final String FORMAT_3GPP = "3gpp"; + + /** + * Indicates a 3GPP2 format SMS message. + * @hide pending API council approval + */ + public static final String FORMAT_3GPP2 = "3gpp2"; + + /** Contains actual SmsMessage. Only public for debugging and for framework layer. + * + * @hide + */ + public SmsMessageBase mWrappedSmsMessage; + + public static class SubmitPdu { + + public byte[] encodedScAddress; // Null if not applicable. + public byte[] encodedMessage; + + public String toString() { + return "SubmitPdu: encodedScAddress = " + + Arrays.toString(encodedScAddress) + + ", encodedMessage = " + + Arrays.toString(encodedMessage); + } + + /** + * @hide + */ + protected SubmitPdu(SubmitPduBase spb) { + this.encodedMessage = spb.encodedMessage; + this.encodedScAddress = spb.encodedScAddress; + } + + } + + private SmsMessage(SmsMessageBase smb) { + mWrappedSmsMessage = smb; + } + + /** + * Create an SmsMessage from a raw PDU. + * + * <p><b>This method will soon be deprecated</b> and all applications which handle + * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast + * intent <b>must</b> now pass the new {@code format} String extra from the intent + * into the new method {@code createFromPdu(byte[], String)} which takes an + * extra format parameter. This is required in order to correctly decode the PDU on + * devices that require support for both 3GPP and 3GPP2 formats at the same time, + * such as dual-mode GSM/CDMA and CDMA/LTE phones. + */ + public static SmsMessage createFromPdu(byte[] pdu) { + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + String format = (PHONE_TYPE_CDMA == activePhone) ? + SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP; + return createFromPdu(pdu, format); + } + + /** + * Create an SmsMessage from a raw PDU with the specified message format. The + * message format is passed in the {@code SMS_RECEIVED_ACTION} as the {@code format} + * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format + * or "3gpp2" for CDMA/LTE messages in 3GPP2 format. + * + * @param pdu the message PDU from the SMS_RECEIVED_ACTION intent + * @param format the format extra from the SMS_RECEIVED_ACTION intent + * @hide pending API council approval + */ + public static SmsMessage createFromPdu(byte[] pdu, String format) { + SmsMessageBase wrappedMessage; + + if (SmsConstants.FORMAT_3GPP2.equals(format)) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu); + } else if (SmsConstants.FORMAT_3GPP.equals(format)) { + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu); + } else { + Log.e(LOG_TAG, "createFromPdu(): unsupported message format " + format); + return null; + } + + return new SmsMessage(wrappedMessage); + } + + /** + * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the + * +CMT unsolicited response (PDU mode, of course) + * +CMT: [<alpha>],<length><CR><LF><pdu> + * + * Only public for debugging and for RIL + * + * {@hide} + */ + public static SmsMessage newFromCMT(String[] lines) { + // received SMS in 3GPP format + SmsMessageBase wrappedMessage = + com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines); + + return new SmsMessage(wrappedMessage); + } + + /** @hide */ + public static SmsMessage newFromParcel(Parcel p) { + // received SMS in 3GPP2 format + SmsMessageBase wrappedMessage = + com.android.internal.telephony.cdma.SmsMessage.newFromParcel(p); + + return new SmsMessage(wrappedMessage); + } + + /** + * Create an SmsMessage from an SMS EF record. + * + * @param index Index of SMS record. This should be index in ArrayList + * returned by SmsManager.getAllMessagesFromSim + 1. + * @param data Record data. + * @return An SmsMessage representing the record. + * + * @hide + */ + public static SmsMessage createFromEfRecord(int index, byte[] data) { + SmsMessageBase wrappedMessage; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord( + index, data); + } else { + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord( + index, data); + } + + return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null; + } + + /** + * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the + * length in bytes (not hex chars) less the SMSC header + * + * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices. + * We should probably deprecate it and remove the obsolete test case. + */ + public static int getTPLayerLengthForPDU(String pdu) { + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu); + } else { + return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu); + } + } + + /* + * TODO(cleanup): It would make some sense if the result of + * preprocessing a message to determine the proper encoding (i.e. + * the resulting data structure from calculateLength) could be + * passed as an argument to the actual final encoding function. + * This would better ensure that the logic behind size calculation + * actually matched the encoding. + */ + + /** + * Calculates the number of SMS's required to encode the message body and + * the number of characters remaining until the next message. + * + * @param msgBody the message to encode + * @param use7bitOnly if true, characters that are not part of the + * radio-specific 7-bit encoding are counted as single + * space chars. If false, and if the messageBody contains + * non-7-bit encodable characters, length is calculated + * using a 16-bit encoding. + * @return an int[4] with int[0] being the number of SMS's + * required, int[1] the number of code units used, and + * int[2] is the number of code units remaining until the + * next message. int[3] is an indicator of the encoding + * code unit size (see the ENCODING_* definitions in SmsConstants) + */ + public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) { + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ? + com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly) : + com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly); + int ret[] = new int[4]; + ret[0] = ted.msgCount; + ret[1] = ted.codeUnitCount; + ret[2] = ted.codeUnitsRemaining; + ret[3] = ted.codeUnitSize; + return ret; + } + + /** + * Divide a message text into several fragments, none bigger than + * the maximum SMS message text size. + * + * @param text text, must not be null. + * @return an <code>ArrayList</code> of strings that, in order, + * comprise the original msg text + * + * @hide + */ + public static ArrayList<String> fragmentText(String text) { + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ? + com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false) : + com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false); + + // TODO(cleanup): The code here could be rolled into the logic + // below cleanly if these MAX_* constants were defined more + // flexibly... + + int limit; + if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) { + int udhLength; + if (ted.languageTable != 0 && ted.languageShiftTable != 0) { + udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES; + } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) { + udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE; + } else { + udhLength = 0; + } + + if (ted.msgCount > 1) { + udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE; + } + + if (udhLength != 0) { + udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH; + } + + limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength; + } else { + if (ted.msgCount > 1) { + limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER; + } else { + limit = SmsConstants.MAX_USER_DATA_BYTES; + } + } + + int pos = 0; // Index in code units. + int textLen = text.length(); + ArrayList<String> result = new ArrayList<String>(ted.msgCount); + while (pos < textLen) { + int nextPos = 0; // Counts code units. + if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) { + if (activePhone == PHONE_TYPE_CDMA && ted.msgCount == 1) { + // For a singleton CDMA message, the encoding must be ASCII... + nextPos = pos + Math.min(limit, textLen - pos); + } else { + // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode). + nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit, + ted.languageTable, ted.languageShiftTable); + } + } else { // Assume unicode. + nextPos = pos + Math.min(limit / 2, textLen - pos); + } + if ((nextPos <= pos) || (nextPos > textLen)) { + Log.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " + + nextPos + " >= " + textLen + ")"); + break; + } + result.add(text.substring(pos, nextPos)); + pos = nextPos; + } + return result; + } + + /** + * Calculates the number of SMS's required to encode the message body and + * the number of characters remaining until the next message, given the + * current encoding. + * + * @param messageBody the message to encode + * @param use7bitOnly if true, characters that are not part of the radio + * specific (GSM / CDMA) alphabet encoding are converted to as a + * single space characters. If false, a messageBody containing + * non-GSM or non-CDMA alphabet characters are encoded using + * 16-bit encoding. + * @return an int[4] with int[0] being the number of SMS's required, int[1] + * the number of code units used, and int[2] is the number of code + * units remaining until the next message. int[3] is the encoding + * type that should be used for the message. + */ + public static int[] calculateLength(String messageBody, boolean use7bitOnly) { + return calculateLength((CharSequence)messageBody, use7bitOnly); + } + + /* + * TODO(cleanup): It looks like there is now no useful reason why + * apps should generate pdus themselves using these routines, + * instead of handing the raw data to SMSDispatcher (and thereby + * have the phone process do the encoding). Moreover, CDMA now + * has shared state (in the form of the msgId system property) + * which can only be modified by the phone process, and hence + * makes the output of these routines incorrect. Since they now + * serve no purpose, they should probably just return null + * directly, and be deprecated. Going further in that direction, + * the above parsers of serialized pdu data should probably also + * be gotten rid of, hiding all but the necessarily visible + * structured data from client apps. A possible concern with + * doing this is that apps may be using these routines to generate + * pdus that are then sent elsewhere, some network server, for + * example, and that always returning null would thereby break + * otherwise useful apps. + */ + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message. + * This method will not attempt to use any GSM national language 7 bit encodings. + * + * @param scAddress Service Centre address. Null means use default. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, boolean statusReportRequested) { + SubmitPduBase spb; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested, null); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested); + } + + return new SubmitPdu(spb); + } + + /** + * Get an SMS-SUBMIT PDU for a data message to a destination address & port. + * This method will not attempt to use any GSM national language 7 bit encodings. + * + * @param scAddress Service Centre address. null == use default + * @param destinationAddress the address of the destination for the message + * @param destinationPort the port to deliver the message to at the + * destination + * @param data the data for the message + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, short destinationPort, byte[] data, + boolean statusReportRequested) { + SubmitPduBase spb; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, destinationPort, data, statusReportRequested); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, destinationPort, data, statusReportRequested); + } + + return new SubmitPdu(spb); + } + + /** + * Returns the address of the SMS service center that relayed this message + * or null if there is none. + */ + public String getServiceCenterAddress() { + return mWrappedSmsMessage.getServiceCenterAddress(); + } + + /** + * Returns the originating address (sender) of this SMS message in String + * form or null if unavailable + */ + public String getOriginatingAddress() { + return mWrappedSmsMessage.getOriginatingAddress(); + } + + /** + * Returns the originating address, or email from address if this message + * was from an email gateway. Returns null if originating address + * unavailable. + */ + public String getDisplayOriginatingAddress() { + return mWrappedSmsMessage.getDisplayOriginatingAddress(); + } + + /** + * Returns the message body as a String, if it exists and is text based. + * @return message body is there is one, otherwise null + */ + public String getMessageBody() { + return mWrappedSmsMessage.getMessageBody(); + } + + /** + * Returns the class of this message. + */ + public MessageClass getMessageClass() { + switch(mWrappedSmsMessage.getMessageClass()) { + case CLASS_0: return MessageClass.CLASS_0; + case CLASS_1: return MessageClass.CLASS_1; + case CLASS_2: return MessageClass.CLASS_2; + case CLASS_3: return MessageClass.CLASS_3; + default: return MessageClass.UNKNOWN; + + } + } + + /** + * Returns the message body, or email message body if this message was from + * an email gateway. Returns null if message body unavailable. + */ + public String getDisplayMessageBody() { + return mWrappedSmsMessage.getDisplayMessageBody(); + } + + /** + * Unofficial convention of a subject line enclosed in parens empty string + * if not present + */ + public String getPseudoSubject() { + return mWrappedSmsMessage.getPseudoSubject(); + } + + /** + * Returns the service centre timestamp in currentTimeMillis() format + */ + public long getTimestampMillis() { + return mWrappedSmsMessage.getTimestampMillis(); + } + + /** + * Returns true if message is an email. + * + * @return true if this message came through an email gateway and email + * sender / subject / parsed body are available + */ + public boolean isEmail() { + return mWrappedSmsMessage.isEmail(); + } + + /** + * @return if isEmail() is true, body of the email sent through the gateway. + * null otherwise + */ + public String getEmailBody() { + return mWrappedSmsMessage.getEmailBody(); + } + + /** + * @return if isEmail() is true, email from address of email sent through + * the gateway. null otherwise + */ + public String getEmailFrom() { + return mWrappedSmsMessage.getEmailFrom(); + } + + /** + * Get protocol identifier. + */ + public int getProtocolIdentifier() { + return mWrappedSmsMessage.getProtocolIdentifier(); + } + + /** + * See TS 23.040 9.2.3.9 returns true if this is a "replace short message" + * SMS + */ + public boolean isReplace() { + return mWrappedSmsMessage.isReplace(); + } + + /** + * Returns true for CPHS MWI toggle message. + * + * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section + * B.4.2 + */ + public boolean isCphsMwiMessage() { + return mWrappedSmsMessage.isCphsMwiMessage(); + } + + /** + * returns true if this message is a CPHS voicemail / message waiting + * indicator (MWI) clear message + */ + public boolean isMWIClearMessage() { + return mWrappedSmsMessage.isMWIClearMessage(); + } + + /** + * returns true if this message is a CPHS voicemail / message waiting + * indicator (MWI) set message + */ + public boolean isMWISetMessage() { + return mWrappedSmsMessage.isMWISetMessage(); + } + + /** + * returns true if this message is a "Message Waiting Indication Group: + * Discard Message" notification and should not be stored. + */ + public boolean isMwiDontStore() { + return mWrappedSmsMessage.isMwiDontStore(); + } + + /** + * returns the user data section minus the user data header if one was + * present. + */ + public byte[] getUserData() { + return mWrappedSmsMessage.getUserData(); + } + + /** + * Returns the raw PDU for the message. + * + * @return the raw PDU for the message. + */ + public byte[] getPdu() { + return mWrappedSmsMessage.getPdu(); + } + + /** + * Returns the status of the message on the SIM (read, unread, sent, unsent). + * + * @return the status of the message on the SIM. These are: + * SmsManager.STATUS_ON_SIM_FREE + * SmsManager.STATUS_ON_SIM_READ + * SmsManager.STATUS_ON_SIM_UNREAD + * SmsManager.STATUS_ON_SIM_SEND + * SmsManager.STATUS_ON_SIM_UNSENT + * @deprecated Use getStatusOnIcc instead. + */ + @Deprecated public int getStatusOnSim() { + return mWrappedSmsMessage.getStatusOnIcc(); + } + + /** + * Returns the status of the message on the ICC (read, unread, sent, unsent). + * + * @return the status of the message on the ICC. These are: + * SmsManager.STATUS_ON_ICC_FREE + * SmsManager.STATUS_ON_ICC_READ + * SmsManager.STATUS_ON_ICC_UNREAD + * SmsManager.STATUS_ON_ICC_SEND + * SmsManager.STATUS_ON_ICC_UNSENT + */ + public int getStatusOnIcc() { + return mWrappedSmsMessage.getStatusOnIcc(); + } + + /** + * Returns the record index of the message on the SIM (1-based index). + * @return the record index of the message on the SIM, or -1 if this + * SmsMessage was not created from a SIM SMS EF record. + * @deprecated Use getIndexOnIcc instead. + */ + @Deprecated public int getIndexOnSim() { + return mWrappedSmsMessage.getIndexOnIcc(); + } + + /** + * Returns the record index of the message on the ICC (1-based index). + * @return the record index of the message on the ICC, or -1 if this + * SmsMessage was not created from a ICC SMS EF record. + */ + public int getIndexOnIcc() { + return mWrappedSmsMessage.getIndexOnIcc(); + } + + /** + * GSM: + * For an SMS-STATUS-REPORT message, this returns the status field from + * the status report. This field indicates the status of a previously + * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a + * description of values. + * CDMA: + * For not interfering with status codes from GSM, the value is + * shifted to the bits 31-16. + * The value is composed of an error class (bits 25-24) and a status code (bits 23-16). + * Possible codes are described in C.S0015-B, v2.0, 4.5.21. + * + * @return 0 indicates the previously sent message was received. + * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21 + * for a description of other possible values. + */ + public int getStatus() { + return mWrappedSmsMessage.getStatus(); + } + + /** + * Return true iff the message is a SMS-STATUS-REPORT message. + */ + public boolean isStatusReportMessage() { + return mWrappedSmsMessage.isStatusReportMessage(); + } + + /** + * Returns true iff the <code>TP-Reply-Path</code> bit is set in + * this message. + */ + public boolean isReplyPathPresent() { + return mWrappedSmsMessage.isReplyPathPresent(); + } +} |