/*
 * 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 com.android.internal.telephony.cdma;

import android.os.Parcel;
import android.os.SystemProperties;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.telephony.Rlog;
import android.util.Log;
import android.text.TextUtils;
import android.content.res.Resources;

import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.SmsAddress;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.HexDump;
import com.android.internal.telephony.Sms7BitEncodingTranslator;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

/**
 * TODO(cleanup): these constants are disturbing... are they not just
 * different interpretations on one number?  And if we did not have
 * terrible class name overlap, they would not need to be directly
 * imported like this.  The class in this file could just as well be
 * named CdmaSmsMessage, could it not?
 */

/**
 * TODO(cleanup): internally returning null in many places makes
 * debugging very hard (among many other reasons) and should be made
 * more meaningful (replaced with exceptions for example).  Null
 * returns should only occur at the very outside of the module/class
 * scope.
 */

A Short Message Service message.
/** * A Short Message Service message. * */
public class SmsMessage extends SmsMessageBase { static final String LOG_TAG = "SmsMessage"; static private final String LOGGABLE_TAG = "CDMA:SMS"; private static final boolean VDBG = false; private final static byte TELESERVICE_IDENTIFIER = 0x00; private final static byte SERVICE_CATEGORY = 0x01; private final static byte ORIGINATING_ADDRESS = 0x02; private final static byte ORIGINATING_SUB_ADDRESS = 0x03; private final static byte DESTINATION_ADDRESS = 0x04; private final static byte DESTINATION_SUB_ADDRESS = 0x05; private final static byte BEARER_REPLY_OPTION = 0x06; private final static byte CAUSE_CODES = 0x07; private final static byte BEARER_DATA = 0x08;
Status of a previously submitted SMS. This field applies to SMS Delivery Acknowledge messages. 0 indicates success; Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0. See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
/** * Status of a previously submitted SMS. * This field applies to SMS Delivery Acknowledge messages. 0 indicates success; * Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0. * See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values. */
private int status;
Specifies if a return of an acknowledgment is requested for send SMS
/** Specifies if a return of an acknowledgment is requested for send SMS */
private static final int RETURN_NO_ACK = 0; private static final int RETURN_ACK = 1;
Supported priority modes for CDMA SMS messages (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
/** * Supported priority modes for CDMA SMS messages * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) */
private static final int PRIORITY_NORMAL = 0x0; private static final int PRIORITY_INTERACTIVE = 0x1; private static final int PRIORITY_URGENT = 0x2; private static final int PRIORITY_EMERGENCY = 0x3; private SmsEnvelope mEnvelope; private BearerData mBearerData;
@hide
/** @hide */
public SmsMessage(SmsAddress addr, SmsEnvelope env) { mOriginatingAddress = addr; mEnvelope = env; createPdu(); } public SmsMessage() {} public static class SubmitPdu extends SubmitPduBase { }
Create an SmsMessage from a raw PDU. Note: In CDMA the PDU is just a byte representation of the received Sms.
/** * Create an SmsMessage from a raw PDU. * Note: In CDMA the PDU is just a byte representation of the received Sms. */
public static SmsMessage createFromPdu(byte[] pdu) { SmsMessage msg = new SmsMessage(); try { msg.parsePdu(pdu); return msg; } catch (RuntimeException ex) { Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); return null; } catch (OutOfMemoryError e) { Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e); return null; } }
Create an SmsMessage from an SMS EF record.
Params:
  • index – Index of SMS record. This should be index in ArrayList returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
  • data – Record data.
Returns:An SmsMessage representing the record.
@hide
/** * Create an SmsMessage from an SMS EF record. * * @param index Index of SMS record. This should be index in ArrayList * returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1. * @param data Record data. * @return An SmsMessage representing the record. * * @hide */
public static SmsMessage createFromEfRecord(int index, byte[] data) { try { SmsMessage msg = new SmsMessage(); msg.mIndexOnIcc = index; // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT, // or STORED_UNSENT // See 3GPP2 C.S0023 3.4.27 if ((data[0] & 1) == 0) { Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record"); return null; } else { msg.mStatusOnIcc = data[0] & 0x07; } // Second byte is the MSG_LEN, length of the message // See 3GPP2 C.S0023 3.4.27 int size = data[1] & 0xFF; // Note: Data may include trailing FF's. That's OK; message // should still parse correctly. byte[] pdu = new byte[size]; System.arraycopy(data, 2, pdu, 0, size); // the message has to be parsed before it can be displayed // see gsm.SmsMessage msg.parsePduFromEfRecord(pdu); return msg; } catch (RuntimeException ex) { Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); return null; } }
Note: This function is a GSM specific functionality which is not supported in CDMA mode.
/** * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */
public static int getTPLayerLengthForPDU(String pdu) { Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode."); return 0; } /** * TODO(cleanup): why do getSubmitPdu methods take an scAddr input * and do nothing with it? GSM allows us to specify a SC (eg, * when responding to an SMS that explicitly requests the response * is sent to a specific SC), or pass null to use the default * value. Is there no similar notion in CDMA? Or do we just not * have it hooked up? */
Get an SMS-SUBMIT PDU for a destination address and a message
Params:
  • scAddr – Service Centre address. Null means use default.
  • destAddr – Address of the recipient.
  • message – String representation of the message payload.
  • statusReportRequested – Indicates whether a report is requested for this message.
  • smsHeader – Array containing the data for the User Data Header, preceded by the Element Identifiers.
Returns:a SubmitPdu containing the encoded SC address, if applicable, and the encoded message. Returns null on encode error.
@hide
/** * Get an SMS-SUBMIT PDU for a destination address and a message * * @param scAddr Service Centre address. Null means use default. * @param destAddr Address of the recipient. * @param message String representation of the message payload. * @param statusReportRequested Indicates whether a report is requested for this message. * @param smsHeader Array containing the data for the User Data Header, preceded * by the Element Identifiers. * @return a <code>SubmitPdu</code> containing the encoded SC * address, if applicable, and the encoded message. * Returns null on encode error. * @hide */
public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader) { return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1); }
Get an SMS-SUBMIT PDU for a destination address and a message
Params:
  • scAddr – Service Centre address. Null means use default.
  • destAddr – Address of the recipient.
  • message – String representation of the message payload.
  • statusReportRequested – Indicates whether a report is requested for this message.
  • smsHeader – Array containing the data for the User Data Header, preceded by the Element Identifiers.
  • priority – Priority level of the message
Returns:a SubmitPdu containing the encoded SC address, if applicable, and the encoded message. Returns null on encode error.
@hide
/** * Get an SMS-SUBMIT PDU for a destination address and a message * * @param scAddr Service Centre address. Null means use default. * @param destAddr Address of the recipient. * @param message String representation of the message payload. * @param statusReportRequested Indicates whether a report is requested for this message. * @param smsHeader Array containing the data for the User Data Header, preceded * by the Element Identifiers. * @param priority Priority level of the message * @return a <code>SubmitPdu</code> containing the encoded SC * address, if applicable, and the encoded message. * Returns null on encode error. * @hide */
public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority) { /** * TODO(cleanup): Do we really want silent failure like this? * Would it not be much more reasonable to make sure we don't * call this function if we really want nothing done? */ if (message == null || destAddr == null) { return null; } UserData uData = new UserData(); uData.payloadStr = message; uData.userDataHeader = smsHeader; return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority); }
Get an SMS-SUBMIT PDU for a data message to a destination address and port.
Params:
  • scAddr – Service Centre address. null == use default
  • destAddr – the address of the destination for the message
  • destPort – the port to deliver the message to at the destination
  • data – the data for the message
Returns:a SubmitPdu containing the encoded SC address, if applicable, and the encoded message. Returns null on encode error.
/** * Get an SMS-SUBMIT PDU for a data message to a destination address and port. * * @param scAddr Service Centre address. null == use default * @param destAddr the address of the destination for the message * @param destPort 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 scAddr, String destAddr, int destPort, byte[] data, boolean statusReportRequested) { /** * TODO(cleanup): this is not a general-purpose SMS creation * method, but rather something specialized to messages * containing OCTET encoded (meaning non-human-readable) user * data. The name should reflect that, and not just overload. */ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs(); portAddrs.destPort = destPort; portAddrs.origPort = 0; portAddrs.areEightBits = false; SmsHeader smsHeader = new SmsHeader(); smsHeader.portAddrs = portAddrs; UserData uData = new UserData(); uData.userDataHeader = smsHeader; uData.msgEncoding = UserData.ENCODING_OCTET; uData.msgEncodingSet = true; uData.payload = data; return privateGetSubmitPdu(destAddr, statusReportRequested, uData); }
Get an SMS-SUBMIT PDU for a data message to a destination address & port
Params:
  • destAddr – the address of the destination for the message
  • userData – the data for the message
  • statusReportRequested – Indicates whether a report is requested for this message.
Returns:a SubmitPdu containing the encoded SC address, if applicable, and the encoded message. Returns null on encode error.
/** * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port * * @param destAddr the address of the destination for the message * @param userData the data for the message * @param statusReportRequested Indicates whether a report is requested for this 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 destAddr, UserData userData, boolean statusReportRequested) { return privateGetSubmitPdu(destAddr, statusReportRequested, userData); }
Get an SMS-SUBMIT PDU for a data message to a destination address & port
Params:
  • destAddr – the address of the destination for the message
  • userData – the data for the message
  • statusReportRequested – Indicates whether a report is requested for this message.
  • priority – Priority level of the message
Returns:a SubmitPdu containing the encoded SC address, if applicable, and the encoded message. Returns null on encode error.
/** * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port * * @param destAddr the address of the destination for the message * @param userData the data for the message * @param statusReportRequested Indicates whether a report is requested for this message. * @param priority Priority level of 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 destAddr, UserData userData, boolean statusReportRequested, int priority) { return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority); }
Note: This function is a GSM specific functionality which is not supported in CDMA mode.
/** * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */
@Override public int getProtocolIdentifier() { Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode."); // (3GPP TS 23.040): "no interworking, but SME to SME protocol": return 0; }
Note: This function is a GSM specific functionality which is not supported in CDMA mode.
/** * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */
@Override public boolean isReplace() { Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode."); return false; }
{@inheritDoc} Note: This function is a GSM specific functionality which is not supported in CDMA mode.
/** * {@inheritDoc} * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */
@Override public boolean isCphsMwiMessage() { Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode."); return false; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public boolean isMWIClearMessage() { return ((mBearerData != null) && (mBearerData.numberOfMessages == 0)); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public boolean isMWISetMessage() { return ((mBearerData != null) && (mBearerData.numberOfMessages > 0)); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public boolean isMwiDontStore() { return ((mBearerData != null) && (mBearerData.numberOfMessages > 0) && (mBearerData.userData == null)); }
Returns the status for a previously submitted message. For not interfering with status codes from GSM, this status code is shifted to the bits 31-16.
/** * Returns the status for a previously submitted message. * For not interfering with status codes from GSM, this status code is * shifted to the bits 31-16. */
@Override public int getStatus() { return (status << 16); }
Return true iff the bearer data message type is DELIVERY_ACK.
/** Return true iff the bearer data message type is DELIVERY_ACK. */
@Override public boolean isStatusReportMessage() { return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK); }
Note: This function is a GSM specific functionality which is not supported in CDMA mode.
/** * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */
@Override public boolean isReplyPathPresent() { Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode."); return false; }
Calculate the number of septets needed to encode the message.
Params:
  • messageBody – the message to encode
  • use7bitOnly – ignore (but still count) illegal characters if true
  • isEntireMsg – indicates if this is entire msg or a segment in multipart msg
Returns:TextEncodingDetails
/** * Calculate the number of septets needed to encode the message. * * @param messageBody the message to encode * @param use7bitOnly ignore (but still count) illegal characters if true * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg * @return TextEncodingDetails */
public static TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly, boolean isEntireMsg) { CharSequence newMsgBody = null; Resources r = Resources.getSystem(); if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) { newMsgBody = Sms7BitEncodingTranslator.translate(messageBody); } if (TextUtils.isEmpty(newMsgBody)) { newMsgBody = messageBody; } return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg); }
Returns the teleservice type of the message.
Returns:the teleservice: SmsEnvelope.TELESERVICE_NOT_SET, SmsEnvelope.TELESERVICE_WMT, SmsEnvelope.TELESERVICE_WEMT, SmsEnvelope.TELESERVICE_VMN, SmsEnvelope.TELESERVICE_WAP
/** * Returns the teleservice type of the message. * @return the teleservice: * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET}, * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT}, * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT}, * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN}, * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP} */
public int getTeleService() { return mEnvelope.teleService; }
Returns the message type of the message.
Returns:the message type: SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT, SmsEnvelope.MESSAGE_TYPE_BROADCAST, SmsEnvelope.MESSAGE_TYPE_ACKNOWLEDGE,
/** * Returns the message type of the message. * @return the message type: * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT}, * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST}, * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE}, */
public int getMessageType() { // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs. // Use the service category parameter to detect CMAS and other cell broadcast messages. if (mEnvelope.serviceCategory != 0) { return SmsEnvelope.MESSAGE_TYPE_BROADCAST; } else { return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; } }
Decodes pdu to an empty SMS object. In the CDMA case the pdu is just an internal byte stream representation of the SMS Java-object.
See Also:
  • createPdu()
/** * Decodes pdu to an empty SMS object. * In the CDMA case the pdu is just an internal byte stream representation * of the SMS Java-object. * @see #createPdu() */
private void parsePdu(byte[] pdu) { ByteArrayInputStream bais = new ByteArrayInputStream(pdu); DataInputStream dis = new DataInputStream(bais); int length; int bearerDataLength; SmsEnvelope env = new SmsEnvelope(); CdmaSmsAddress addr = new CdmaSmsAddress(); // We currently do not parse subaddress in PDU, but it is required when determining // fingerprint (see getIncomingSmsFingerprint()). CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress(); try { env.messageType = dis.readInt(); env.teleService = dis.readInt(); env.serviceCategory = dis.readInt(); addr.digitMode = dis.readByte(); addr.numberMode = dis.readByte(); addr.ton = dis.readByte(); addr.numberPlan = dis.readByte(); length = dis.readUnsignedByte(); addr.numberOfDigits = length; // sanity check on the length if (length > pdu.length) { throw new RuntimeException( "createFromPdu: Invalid pdu, addr.numberOfDigits " + length + " > pdu len " + pdu.length); } addr.origBytes = new byte[length]; dis.read(addr.origBytes, 0, length); // digits env.bearerReply = dis.readInt(); // CauseCode values: env.replySeqNo = dis.readByte(); env.errorClass = dis.readByte(); env.causeCode = dis.readByte(); //encoded BearerData: bearerDataLength = dis.readInt(); // sanity check on the length if (bearerDataLength > pdu.length) { throw new RuntimeException( "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength + " > pdu len " + pdu.length); } env.bearerData = new byte[bearerDataLength]; dis.read(env.bearerData, 0, bearerDataLength); dis.close(); } catch (IOException ex) { throw new RuntimeException( "createFromPdu: conversion from byte array to object failed: " + ex, ex); } catch (Exception ex) { Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex); } // link the filled objects to this SMS mOriginatingAddress = addr; env.origAddress = addr; env.origSubaddress = subaddr; mEnvelope = env; mPdu = pdu; parseSms(); }
Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
/** * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0 */
private void parsePduFromEfRecord(byte[] pdu) { ByteArrayInputStream bais = new ByteArrayInputStream(pdu); DataInputStream dis = new DataInputStream(bais); SmsEnvelope env = new SmsEnvelope(); CdmaSmsAddress addr = new CdmaSmsAddress(); CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress(); try { env.messageType = dis.readByte(); while (dis.available() > 0) { int parameterId = dis.readByte(); int parameterLen = dis.readUnsignedByte(); byte[] parameterData = new byte[parameterLen]; switch (parameterId) { case TELESERVICE_IDENTIFIER: /* * 16 bit parameter that identifies which upper layer * service access point is sending or should receive * this message */ env.teleService = dis.readUnsignedShort(); Rlog.i(LOG_TAG, "teleservice = " + env.teleService); break; case SERVICE_CATEGORY: /* * 16 bit parameter that identifies type of service as * in 3GPP2 C.S0015-0 Table 3.4.3.2-1 */ env.serviceCategory = dis.readUnsignedShort(); break; case ORIGINATING_ADDRESS: case DESTINATION_ADDRESS: dis.read(parameterData, 0, parameterLen); BitwiseInputStream addrBis = new BitwiseInputStream(parameterData); addr.digitMode = addrBis.read(1); addr.numberMode = addrBis.read(1); int numberType = 0; if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { numberType = addrBis.read(3); addr.ton = numberType; if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) addr.numberPlan = addrBis.read(4); } addr.numberOfDigits = addrBis.read(8); byte[] data = new byte[addr.numberOfDigits]; byte b = 0x00; if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) { /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */ for (int index = 0; index < addr.numberOfDigits; index++) { b = (byte) (0xF & addrBis.read(4)); // convert the value if it is 4-bit DTMF to 8 // bit data[index] = convertDtmfToAscii(b); } } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) { for (int index = 0; index < addr.numberOfDigits; index++) { b = (byte) (0xFF & addrBis.read(8)); data[index] = b; } } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) { if (numberType == 2) Rlog.e(LOG_TAG, "TODO: Originating Addr is email id"); else Rlog.e(LOG_TAG, "TODO: Originating Addr is data network address"); } else { Rlog.e(LOG_TAG, "Originating Addr is of incorrect type"); } } else { Rlog.e(LOG_TAG, "Incorrect Digit mode"); } addr.origBytes = data; Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString()); break; case ORIGINATING_SUB_ADDRESS: case DESTINATION_SUB_ADDRESS: dis.read(parameterData, 0, parameterLen); BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData); subAddr.type = subAddrBis.read(3); subAddr.odd = subAddrBis.readByteArray(1)[0]; int subAddrLen = subAddrBis.read(8); byte[] subdata = new byte[subAddrLen]; for (int index = 0; index < subAddrLen; index++) { b = (byte) (0xFF & subAddrBis.read(4)); // convert the value if it is 4-bit DTMF to 8 bit subdata[index] = convertDtmfToAscii(b); } subAddr.origBytes = subdata; break; case BEARER_REPLY_OPTION: dis.read(parameterData, 0, parameterLen); BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData); env.bearerReply = replyOptBis.read(6); break; case CAUSE_CODES: dis.read(parameterData, 0, parameterLen); BitwiseInputStream ccBis = new BitwiseInputStream(parameterData); env.replySeqNo = ccBis.readByteArray(6)[0]; env.errorClass = ccBis.readByteArray(2)[0]; if (env.errorClass != 0x00) env.causeCode = ccBis.readByteArray(8)[0]; break; case BEARER_DATA: dis.read(parameterData, 0, parameterLen); env.bearerData = parameterData; break; default: throw new Exception("unsupported parameterId (" + parameterId + ")"); } } bais.close(); dis.close(); } catch (Exception ex) { Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex); } // link the filled objects to this SMS mOriginatingAddress = addr; env.origAddress = addr; env.origSubaddress = subAddr; mEnvelope = env; mPdu = pdu; parseSms(); }
Parses a SMS message from its BearerData stream. (mobile-terminated only)
/** * Parses a SMS message from its BearerData stream. (mobile-terminated only) */
public void parseSms() { // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6 // It contains only an 8-bit number with the number of messages waiting if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) { mBearerData = new BearerData(); if (mEnvelope.bearerData != null) { mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0]; } if (VDBG) { Rlog.d(LOG_TAG, "parseSms: get MWI " + Integer.toString(mBearerData.numberOfMessages)); } return; } mBearerData = BearerData.decode(mEnvelope.bearerData); if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { Rlog.d(LOG_TAG, "MT raw BearerData = '" + HexDump.toHexString(mEnvelope.bearerData) + "'"); Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData); } mMessageRef = mBearerData.messageId; if (mBearerData.userData != null) { mUserData = mBearerData.userData.payload; mUserDataHeader = mBearerData.userData.userDataHeader; mMessageBody = mBearerData.userData.payloadStr; } if (mOriginatingAddress != null) { mOriginatingAddress.address = new String(mOriginatingAddress.origBytes); if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) { if (mOriginatingAddress.address.charAt(0) != '+') { mOriginatingAddress.address = "+" + mOriginatingAddress.address; } } if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " + mOriginatingAddress.address); } if (mBearerData.msgCenterTimeStamp != null) { mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true); } if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis); // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1) if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) { // The BearerData MsgStatus subparameter should only be // included for DELIVERY_ACK messages. If it occurred for // other messages, it would be unclear what the status // being reported refers to. The MsgStatus subparameter // is primarily useful to indicate error conditions -- a // message without this subparameter is assumed to // indicate successful delivery (status == 0). if (! mBearerData.messageStatusSet) { Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" + (mUserData == null ? "also missing" : "does have") + " userData)."); status = 0; } else { status = mBearerData.errorClass << 8; status |= mBearerData.messageStatus; } } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) { throw new RuntimeException("Unsupported message type: " + mBearerData.messageType); } if (mMessageBody != null) { if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'"); parseMessageBody(); } else if ((mUserData != null) && VDBG) { Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'"); } }
Parses a broadcast SMS, possibly containing a CMAS alert.
/** * Parses a broadcast SMS, possibly containing a CMAS alert. */
public SmsCbMessage parseBroadcastSms() { BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory); if (bData == null) { Rlog.w(LOG_TAG, "BearerData.decode() returned null"); return null; } if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData)); } String plmn = TelephonyManager.getDefault().getNetworkOperator(); SmsCbLocation location = new SmsCbLocation(plmn); return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2, SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location, mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr, bData.priority, null, bData.cmasWarningInfo); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public SmsConstants.MessageClass getMessageClass() { if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) { return SmsConstants.MessageClass.CLASS_0; } else { return SmsConstants.MessageClass.UNKNOWN; } }
Calculate the next message id, starting at 1 and iteratively incrementing within the range 1..65535 remembering the state via a persistent system property. (See C.S0015-B, v2.0, 4.3.1.5) Since this routine is expected to be accessed via via binder-call, and hence should be thread-safe, it has been synchronized.
/** * Calculate the next message id, starting at 1 and iteratively * incrementing within the range 1..65535 remembering the state * via a persistent system property. (See C.S0015-B, v2.0, * 4.3.1.5) Since this routine is expected to be accessed via via * binder-call, and hence should be thread-safe, it has been * synchronized. */
public synchronized static int getNextMessageId() { // Testing and dialog with partners has indicated that // msgId==0 is (sometimes?) treated specially by lower levels. // Specifically, the ID is not preserved for delivery ACKs. // Hence, avoid 0 -- constraining the range to 1..65535. int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1); String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1); try{ SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId); if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId); Rlog.d(LOG_TAG, "readback gets " + SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID)); } } catch(RuntimeException ex) { Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex); } return msgId; }
Creates BearerData and Envelope from parameters for a Submit SMS.
Returns:byte stream for SubmitPdu.
/** * Creates BearerData and Envelope from parameters for a Submit SMS. * @return byte stream for SubmitPdu. */
private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, UserData userData) { return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1); }
Creates BearerData and Envelope from parameters for a Submit SMS.
Returns:byte stream for SubmitPdu.
/** * Creates BearerData and Envelope from parameters for a Submit SMS. * @return byte stream for SubmitPdu. */
private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, UserData userData, int priority) { /** * TODO(cleanup): give this function a more meaningful name. */ /** * TODO(cleanup): Make returning null from the getSubmitPdu * variations meaningful -- clean up the error feedback * mechanism, and avoid null pointer exceptions. */ /** * North America Plus Code : * Convert + code to 011 and dial out for international SMS */ CdmaSmsAddress destAddr = CdmaSmsAddress.parse( PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr)); if (destAddr == null) return null; BearerData bearerData = new BearerData(); bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT; bearerData.messageId = getNextMessageId(); bearerData.deliveryAckReq = statusReportRequested; bearerData.userAckReq = false; bearerData.readAckReq = false; bearerData.reportReq = false; if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) { bearerData.priorityIndicatorSet = true; bearerData.priority = priority; } bearerData.userData = userData; byte[] encodedBearerData = BearerData.encode(bearerData); if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData); Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'"); } if (encodedBearerData == null) return null; int teleservice = bearerData.hasUserDataHeader ? SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; SmsEnvelope envelope = new SmsEnvelope(); envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; envelope.teleService = teleservice; envelope.destAddress = destAddr; envelope.bearerReply = RETURN_ACK; envelope.bearerData = encodedBearerData; /** * TODO(cleanup): envelope looks to be a pointless class, get * rid of it. Also -- most of the envelope fields set here * are ignored, why? */ try { /** * TODO(cleanup): reference a spec and get rid of the ugly comments */ ByteArrayOutputStream baos = new ByteArrayOutputStream(100); DataOutputStream dos = new DataOutputStream(baos); dos.writeInt(envelope.teleService); dos.writeInt(0); //servicePresent dos.writeInt(0); //serviceCategory dos.write(destAddr.digitMode); dos.write(destAddr.numberMode); dos.write(destAddr.ton); // number_type dos.write(destAddr.numberPlan); dos.write(destAddr.numberOfDigits); dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits // Subaddress is not supported. dos.write(0); //subaddressType dos.write(0); //subaddr_odd dos.write(0); //subaddr_nbr_of_digits dos.write(encodedBearerData.length); dos.write(encodedBearerData, 0, encodedBearerData.length); dos.close(); SubmitPdu pdu = new SubmitPdu(); pdu.encodedMessage = baos.toByteArray(); pdu.encodedScAddress = null; return pdu; } catch(IOException ex) { Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex); } return null; }
Creates byte array (pseudo pdu) from SMS object. Note: Do not call this method more than once per object!
@hide
/** * Creates byte array (pseudo pdu) from SMS object. * Note: Do not call this method more than once per object! * @hide */
public void createPdu() { SmsEnvelope env = mEnvelope; CdmaSmsAddress addr = env.origAddress; ByteArrayOutputStream baos = new ByteArrayOutputStream(100); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos)); try { dos.writeInt(env.messageType); dos.writeInt(env.teleService); dos.writeInt(env.serviceCategory); dos.writeByte(addr.digitMode); dos.writeByte(addr.numberMode); dos.writeByte(addr.ton); dos.writeByte(addr.numberPlan); dos.writeByte(addr.numberOfDigits); dos.write(addr.origBytes, 0, addr.origBytes.length); // digits dos.writeInt(env.bearerReply); // CauseCode values: dos.writeByte(env.replySeqNo); dos.writeByte(env.errorClass); dos.writeByte(env.causeCode); //encoded BearerData: dos.writeInt(env.bearerData.length); dos.write(env.bearerData, 0, env.bearerData.length); dos.close(); /** * TODO(cleanup) -- The mPdu field is managed in * a fragile manner, and it would be much nicer if * accessing the serialized representation used a less * fragile mechanism. Maybe the getPdu method could * generate a representation if there was not yet one? */ mPdu = baos.toByteArray(); } catch (IOException ex) { Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex); } }
Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
@hide
/** * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character * @hide */
public static byte convertDtmfToAscii(byte dtmfDigit) { byte asciiDigit; switch (dtmfDigit) { case 0: asciiDigit = 68; break; // 'D' case 1: asciiDigit = 49; break; // '1' case 2: asciiDigit = 50; break; // '2' case 3: asciiDigit = 51; break; // '3' case 4: asciiDigit = 52; break; // '4' case 5: asciiDigit = 53; break; // '5' case 6: asciiDigit = 54; break; // '6' case 7: asciiDigit = 55; break; // '7' case 8: asciiDigit = 56; break; // '8' case 9: asciiDigit = 57; break; // '9' case 10: asciiDigit = 48; break; // '0' case 11: asciiDigit = 42; break; // '*' case 12: asciiDigit = 35; break; // '#' case 13: asciiDigit = 65; break; // 'A' case 14: asciiDigit = 66; break; // 'B' case 15: asciiDigit = 67; break; // 'C' default: asciiDigit = 32; // Invalid DTMF code break; } return asciiDigit; }
This function shall be called to get the number of voicemails.
@hide
/** This function shall be called to get the number of voicemails. * @hide */
public int getNumOfVoicemails() { return mBearerData.numberOfMessages; }
Returns a byte array that can be use to uniquely identify a received SMS message. C.S0015-B 4.3.1.6 Unique Message Identification.
Returns:byte array uniquely identifying the message.
@hide
/** * Returns a byte array that can be use to uniquely identify a received SMS message. * C.S0015-B 4.3.1.6 Unique Message Identification. * * @return byte array uniquely identifying the message. * @hide */
public byte[] getIncomingSmsFingerprint() { ByteArrayOutputStream output = new ByteArrayOutputStream(); output.write(mEnvelope.serviceCategory); output.write(mEnvelope.teleService); output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length); output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length); // subaddress is not set when parsing some MT SMS. if (mEnvelope.origSubaddress != null && mEnvelope.origSubaddress.origBytes != null) { output.write(mEnvelope.origSubaddress.origBytes, 0, mEnvelope.origSubaddress.origBytes.length); } return output.toByteArray(); }
Returns the list of service category program data, if present.
Returns:a list of CdmaSmsCbProgramData objects, or null if not present
@hide
/** * Returns the list of service category program data, if present. * @return a list of CdmaSmsCbProgramData objects, or null if not present * @hide */
public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() { return mBearerData.serviceCategoryProgramData; } }