/* * 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.telephony.Rlog; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; import com.android.internal.telephony.SmsConstants; 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 = "SmsMessage"; /** * 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; @Override 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. * *
This method will soon be deprecated and all applications which handle
* incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
* intent must 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 {
Rlog.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>],ArrayList
of strings that, in order,
* comprise the original msg text
*
* @hide
*/
public static ArrayListSubmitPdu
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 SubmitPdu
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 TP-Reply-Path
bit is set in
* this message.
*/
public boolean isReplyPathPresent() {
return mWrappedSmsMessage.isReplyPathPresent();
}
}