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
17package com.android.internal.telephony.cdma;
18
19import android.os.Parcel;
20import android.os.SystemProperties;
21import android.telephony.PhoneNumberUtils;
22import android.telephony.SmsCbLocation;
23import android.telephony.SmsCbMessage;
24import android.telephony.TelephonyManager;
25import android.telephony.cdma.CdmaSmsCbProgramData;
26import android.telephony.Rlog;
27import android.util.Log;
28import android.text.TextUtils;
29import android.content.res.Resources;
30
31import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
32import com.android.internal.telephony.SmsAddress;
33import com.android.internal.telephony.SmsConstants;
34import com.android.internal.telephony.SmsHeader;
35import com.android.internal.telephony.SmsMessageBase;
36import com.android.internal.telephony.TelephonyProperties;
37import com.android.internal.telephony.cdma.sms.BearerData;
38import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
39import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
40import com.android.internal.telephony.cdma.sms.SmsEnvelope;
41import com.android.internal.telephony.cdma.sms.UserData;
42import com.android.internal.telephony.uicc.IccUtils;
43import com.android.internal.util.BitwiseInputStream;
44import com.android.internal.util.HexDump;
45import com.android.internal.telephony.Sms7BitEncodingTranslator;
46
47import java.io.BufferedOutputStream;
48import java.io.ByteArrayInputStream;
49import java.io.ByteArrayOutputStream;
50import java.io.DataInputStream;
51import java.io.DataOutputStream;
52import java.io.IOException;
53import java.util.ArrayList;
54
55/**
56 * TODO(cleanup): these constants are disturbing... are they not just
57 * different interpretations on one number?  And if we did not have
58 * terrible class name overlap, they would not need to be directly
59 * imported like this.  The class in this file could just as well be
60 * named CdmaSmsMessage, could it not?
61 */
62
63/**
64 * TODO(cleanup): internally returning null in many places makes
65 * debugging very hard (among many other reasons) and should be made
66 * more meaningful (replaced with exceptions for example).  Null
67 * returns should only occur at the very outside of the module/class
68 * scope.
69 */
70
71/**
72 * A Short Message Service message.
73 *
74 */
75public class SmsMessage extends SmsMessageBase {
76    static final String LOG_TAG = "SmsMessage";
77    static private final String LOGGABLE_TAG = "CDMA:SMS";
78    private static final boolean VDBG = false;
79
80    private final static byte TELESERVICE_IDENTIFIER                    = 0x00;
81    private final static byte SERVICE_CATEGORY                          = 0x01;
82    private final static byte ORIGINATING_ADDRESS                       = 0x02;
83    private final static byte ORIGINATING_SUB_ADDRESS                   = 0x03;
84    private final static byte DESTINATION_ADDRESS                       = 0x04;
85    private final static byte DESTINATION_SUB_ADDRESS                   = 0x05;
86    private final static byte BEARER_REPLY_OPTION                       = 0x06;
87    private final static byte CAUSE_CODES                               = 0x07;
88    private final static byte BEARER_DATA                               = 0x08;
89
90    /**
91     *  Status of a previously submitted SMS.
92     *  This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
93     *  Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
94     *  See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
95     */
96    private int status;
97
98    /** Specifies if a return of an acknowledgment is requested for send SMS */
99    private static final int RETURN_NO_ACK  = 0;
100    private static final int RETURN_ACK     = 1;
101
102    private SmsEnvelope mEnvelope;
103    private BearerData mBearerData;
104
105    /** @hide */
106    public SmsMessage(SmsAddress addr, SmsEnvelope env) {
107        mOriginatingAddress = addr;
108        mEnvelope = env;
109        createPdu();
110    }
111
112    public SmsMessage() {}
113
114    public static class SubmitPdu extends SubmitPduBase {
115    }
116
117    /**
118     * Create an SmsMessage from a raw PDU.
119     * Note: In CDMA the PDU is just a byte representation of the received Sms.
120     */
121    public static SmsMessage createFromPdu(byte[] pdu) {
122        SmsMessage msg = new SmsMessage();
123
124        try {
125            msg.parsePdu(pdu);
126            return msg;
127        } catch (RuntimeException ex) {
128            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
129            return null;
130        } catch (OutOfMemoryError e) {
131            Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
132            return null;
133        }
134    }
135
136    /**
137     * Create an SmsMessage from an SMS EF record.
138     *
139     * @param index Index of SMS record. This should be index in ArrayList
140     *              returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
141     * @param data Record data.
142     * @return An SmsMessage representing the record.
143     *
144     * @hide
145     */
146    public static SmsMessage createFromEfRecord(int index, byte[] data) {
147        try {
148            SmsMessage msg = new SmsMessage();
149
150            msg.mIndexOnIcc = index;
151
152            // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
153            // or STORED_UNSENT
154            // See 3GPP2 C.S0023 3.4.27
155            if ((data[0] & 1) == 0) {
156                Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
157                return null;
158            } else {
159                msg.mStatusOnIcc = data[0] & 0x07;
160            }
161
162            // Second byte is the MSG_LEN, length of the message
163            // See 3GPP2 C.S0023 3.4.27
164            int size = data[1];
165
166            // Note: Data may include trailing FF's.  That's OK; message
167            // should still parse correctly.
168            byte[] pdu = new byte[size];
169            System.arraycopy(data, 2, pdu, 0, size);
170            // the message has to be parsed before it can be displayed
171            // see gsm.SmsMessage
172            msg.parsePduFromEfRecord(pdu);
173            return msg;
174        } catch (RuntimeException ex) {
175            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
176            return null;
177        }
178
179    }
180
181    /**
182     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
183     */
184    public static int getTPLayerLengthForPDU(String pdu) {
185        Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
186        return 0;
187    }
188
189    /**
190     * TODO(cleanup): why do getSubmitPdu methods take an scAddr input
191     * and do nothing with it?  GSM allows us to specify a SC (eg,
192     * when responding to an SMS that explicitly requests the response
193     * is sent to a specific SC), or pass null to use the default
194     * value.  Is there no similar notion in CDMA? Or do we just not
195     * have it hooked up?
196     */
197
198    /**
199     * Get an SMS-SUBMIT PDU for a destination address and a message
200     *
201     * @param scAddr                Service Centre address.  Null means use default.
202     * @param destAddr              Address of the recipient.
203     * @param message               String representation of the message payload.
204     * @param statusReportRequested Indicates whether a report is requested for this message.
205     * @param smsHeader             Array containing the data for the User Data Header, preceded
206     *                              by the Element Identifiers.
207     * @return a <code>SubmitPdu</code> containing the encoded SC
208     *         address, if applicable, and the encoded message.
209     *         Returns null on encode error.
210     * @hide
211     */
212    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
213            boolean statusReportRequested, SmsHeader smsHeader) {
214
215        /**
216         * TODO(cleanup): Do we really want silent failure like this?
217         * Would it not be much more reasonable to make sure we don't
218         * call this function if we really want nothing done?
219         */
220        if (message == null || destAddr == null) {
221            return null;
222        }
223
224        UserData uData = new UserData();
225        uData.payloadStr = message;
226        uData.userDataHeader = smsHeader;
227        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
228    }
229
230    /**
231     * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
232     *
233     * @param scAddr Service Centre address. null == use default
234     * @param destAddr the address of the destination for the message
235     * @param destPort the port to deliver the message to at the
236     *        destination
237     * @param data the data for the message
238     * @return a <code>SubmitPdu</code> containing the encoded SC
239     *         address, if applicable, and the encoded message.
240     *         Returns null on encode error.
241     */
242    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
243            byte[] data, boolean statusReportRequested) {
244
245        /**
246         * TODO(cleanup): this is not a general-purpose SMS creation
247         * method, but rather something specialized to messages
248         * containing OCTET encoded (meaning non-human-readable) user
249         * data.  The name should reflect that, and not just overload.
250         */
251
252        SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
253        portAddrs.destPort = destPort;
254        portAddrs.origPort = 0;
255        portAddrs.areEightBits = false;
256
257        SmsHeader smsHeader = new SmsHeader();
258        smsHeader.portAddrs = portAddrs;
259
260        UserData uData = new UserData();
261        uData.userDataHeader = smsHeader;
262        uData.msgEncoding = UserData.ENCODING_OCTET;
263        uData.msgEncodingSet = true;
264        uData.payload = data;
265
266        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
267    }
268
269    /**
270     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
271     *
272     * @param destAddr the address of the destination for the message
273     * @param userData the data for the message
274     * @param statusReportRequested Indicates whether a report is requested for this message.
275     * @return a <code>SubmitPdu</code> containing the encoded SC
276     *         address, if applicable, and the encoded message.
277     *         Returns null on encode error.
278     */
279    public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
280            boolean statusReportRequested) {
281        return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
282    }
283
284    /**
285     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
286     */
287    @Override
288    public int getProtocolIdentifier() {
289        Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
290        // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
291        return 0;
292    }
293
294    /**
295     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
296     */
297    @Override
298    public boolean isReplace() {
299        Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
300        return false;
301    }
302
303    /**
304     * {@inheritDoc}
305     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
306     */
307    @Override
308    public boolean isCphsMwiMessage() {
309        Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
310        return false;
311    }
312
313    /**
314     * {@inheritDoc}
315     */
316    @Override
317    public boolean isMWIClearMessage() {
318        return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
319    }
320
321    /**
322     * {@inheritDoc}
323     */
324    @Override
325    public boolean isMWISetMessage() {
326        return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
327    }
328
329    /**
330     * {@inheritDoc}
331     */
332    @Override
333    public boolean isMwiDontStore() {
334        return ((mBearerData != null) &&
335                (mBearerData.numberOfMessages > 0) &&
336                (mBearerData.userData == null));
337    }
338
339    /**
340     * Returns the status for a previously submitted message.
341     * For not interfering with status codes from GSM, this status code is
342     * shifted to the bits 31-16.
343     */
344    @Override
345    public int getStatus() {
346        return (status << 16);
347    }
348
349    /** Return true iff the bearer data message type is DELIVERY_ACK. */
350    @Override
351    public boolean isStatusReportMessage() {
352        return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
353    }
354
355    /**
356     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
357     */
358    @Override
359    public boolean isReplyPathPresent() {
360        Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
361        return false;
362    }
363
364    /**
365     * Calculate the number of septets needed to encode the message.
366     *
367     * @param messageBody the message to encode
368     * @param use7bitOnly ignore (but still count) illegal characters if true
369     * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
370     * @return TextEncodingDetails
371     */
372    public static TextEncodingDetails calculateLength(CharSequence messageBody,
373            boolean use7bitOnly, boolean isEntireMsg) {
374        CharSequence newMsgBody = null;
375        Resources r = Resources.getSystem();
376        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
377            newMsgBody  = Sms7BitEncodingTranslator.translate(messageBody);
378        }
379        if (TextUtils.isEmpty(newMsgBody)) {
380            newMsgBody = messageBody;
381        }
382        return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg);
383    }
384
385    /**
386     * Returns the teleservice type of the message.
387     * @return the teleservice:
388     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
389     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
390     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
391     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
392     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
393    */
394    public int getTeleService() {
395        return mEnvelope.teleService;
396    }
397
398    /**
399     * Returns the message type of the message.
400     * @return the message type:
401     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
402     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
403     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
404    */
405    public int getMessageType() {
406        // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
407        // Use the service category parameter to detect CMAS and other cell broadcast messages.
408        if (mEnvelope.serviceCategory != 0) {
409            return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
410        } else {
411            return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
412        }
413    }
414
415    /**
416     * Decodes pdu to an empty SMS object.
417     * In the CDMA case the pdu is just an internal byte stream representation
418     * of the SMS Java-object.
419     * @see #createPdu()
420     */
421    private void parsePdu(byte[] pdu) {
422        ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
423        DataInputStream dis = new DataInputStream(bais);
424        int length;
425        int bearerDataLength;
426        SmsEnvelope env = new SmsEnvelope();
427        CdmaSmsAddress addr = new CdmaSmsAddress();
428
429        try {
430            env.messageType = dis.readInt();
431            env.teleService = dis.readInt();
432            env.serviceCategory = dis.readInt();
433
434            addr.digitMode = dis.readByte();
435            addr.numberMode = dis.readByte();
436            addr.ton = dis.readByte();
437            addr.numberPlan = dis.readByte();
438
439            length = dis.readUnsignedByte();
440            addr.numberOfDigits = length;
441
442            // sanity check on the length
443            if (length > pdu.length) {
444                throw new RuntimeException(
445                        "createFromPdu: Invalid pdu, addr.numberOfDigits " + length
446                        + " > pdu len " + pdu.length);
447            }
448            addr.origBytes = new byte[length];
449            dis.read(addr.origBytes, 0, length); // digits
450
451            env.bearerReply = dis.readInt();
452            // CauseCode values:
453            env.replySeqNo = dis.readByte();
454            env.errorClass = dis.readByte();
455            env.causeCode = dis.readByte();
456
457            //encoded BearerData:
458            bearerDataLength = dis.readInt();
459            // sanity check on the length
460            if (bearerDataLength > pdu.length) {
461                throw new RuntimeException(
462                        "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
463                        + " > pdu len " + pdu.length);
464            }
465            env.bearerData = new byte[bearerDataLength];
466            dis.read(env.bearerData, 0, bearerDataLength);
467            dis.close();
468        } catch (IOException ex) {
469            throw new RuntimeException(
470                    "createFromPdu: conversion from byte array to object failed: " + ex, ex);
471        } catch (Exception ex) {
472            Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
473        }
474
475        // link the filled objects to this SMS
476        mOriginatingAddress = addr;
477        env.origAddress = addr;
478        mEnvelope = env;
479        mPdu = pdu;
480
481        parseSms();
482    }
483
484    /**
485     * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
486     */
487    private void parsePduFromEfRecord(byte[] pdu) {
488        ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
489        DataInputStream dis = new DataInputStream(bais);
490        SmsEnvelope env = new SmsEnvelope();
491        CdmaSmsAddress addr = new CdmaSmsAddress();
492        CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
493
494        try {
495            env.messageType = dis.readByte();
496
497            while (dis.available() > 0) {
498                int parameterId = dis.readByte();
499                int parameterLen = dis.readUnsignedByte();
500                byte[] parameterData = new byte[parameterLen];
501
502                switch (parameterId) {
503                    case TELESERVICE_IDENTIFIER:
504                        /*
505                         * 16 bit parameter that identifies which upper layer
506                         * service access point is sending or should receive
507                         * this message
508                         */
509                        env.teleService = dis.readUnsignedShort();
510                        Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
511                        break;
512                    case SERVICE_CATEGORY:
513                        /*
514                         * 16 bit parameter that identifies type of service as
515                         * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
516                         */
517                        env.serviceCategory = dis.readUnsignedShort();
518                        break;
519                    case ORIGINATING_ADDRESS:
520                    case DESTINATION_ADDRESS:
521                        dis.read(parameterData, 0, parameterLen);
522                        BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
523                        addr.digitMode = addrBis.read(1);
524                        addr.numberMode = addrBis.read(1);
525                        int numberType = 0;
526                        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
527                            numberType = addrBis.read(3);
528                            addr.ton = numberType;
529
530                            if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
531                                addr.numberPlan = addrBis.read(4);
532                        }
533
534                        addr.numberOfDigits = addrBis.read(8);
535
536                        byte[] data = new byte[addr.numberOfDigits];
537                        byte b = 0x00;
538
539                        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
540                            /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
541                            for (int index = 0; index < addr.numberOfDigits; index++) {
542                                b = (byte) (0xF & addrBis.read(4));
543                                // convert the value if it is 4-bit DTMF to 8
544                                // bit
545                                data[index] = convertDtmfToAscii(b);
546                            }
547                        } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
548                            if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
549                                for (int index = 0; index < addr.numberOfDigits; index++) {
550                                    b = (byte) (0xFF & addrBis.read(8));
551                                    data[index] = b;
552                                }
553
554                            } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
555                                if (numberType == 2)
556                                    Rlog.e(LOG_TAG, "TODO: Originating Addr is email id");
557                                else
558                                    Rlog.e(LOG_TAG,
559                                          "TODO: Originating Addr is data network address");
560                            } else {
561                                Rlog.e(LOG_TAG, "Originating Addr is of incorrect type");
562                            }
563                        } else {
564                            Rlog.e(LOG_TAG, "Incorrect Digit mode");
565                        }
566                        addr.origBytes = data;
567                        Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString());
568                        break;
569                    case ORIGINATING_SUB_ADDRESS:
570                    case DESTINATION_SUB_ADDRESS:
571                        dis.read(parameterData, 0, parameterLen);
572                        BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
573                        subAddr.type = subAddrBis.read(3);
574                        subAddr.odd = subAddrBis.readByteArray(1)[0];
575                        int subAddrLen = subAddrBis.read(8);
576                        byte[] subdata = new byte[subAddrLen];
577                        for (int index = 0; index < subAddrLen; index++) {
578                            b = (byte) (0xFF & subAddrBis.read(4));
579                            // convert the value if it is 4-bit DTMF to 8 bit
580                            subdata[index] = convertDtmfToAscii(b);
581                        }
582                        subAddr.origBytes = subdata;
583                        break;
584                    case BEARER_REPLY_OPTION:
585                        dis.read(parameterData, 0, parameterLen);
586                        BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
587                        env.bearerReply = replyOptBis.read(6);
588                        break;
589                    case CAUSE_CODES:
590                        dis.read(parameterData, 0, parameterLen);
591                        BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
592                        env.replySeqNo = ccBis.readByteArray(6)[0];
593                        env.errorClass = ccBis.readByteArray(2)[0];
594                        if (env.errorClass != 0x00)
595                            env.causeCode = ccBis.readByteArray(8)[0];
596                        break;
597                    case BEARER_DATA:
598                        dis.read(parameterData, 0, parameterLen);
599                        env.bearerData = parameterData;
600                        break;
601                    default:
602                        throw new Exception("unsupported parameterId (" + parameterId + ")");
603                }
604            }
605            bais.close();
606            dis.close();
607        } catch (Exception ex) {
608            Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
609        }
610
611        // link the filled objects to this SMS
612        mOriginatingAddress = addr;
613        env.origAddress = addr;
614        env.origSubaddress = subAddr;
615        mEnvelope = env;
616        mPdu = pdu;
617
618        parseSms();
619    }
620
621    /**
622     * Parses a SMS message from its BearerData stream. (mobile-terminated only)
623     */
624    public void parseSms() {
625        // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
626        // It contains only an 8-bit number with the number of messages waiting
627        if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
628            mBearerData = new BearerData();
629            if (mEnvelope.bearerData != null) {
630                mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
631            }
632            if (VDBG) {
633                Rlog.d(LOG_TAG, "parseSms: get MWI " +
634                      Integer.toString(mBearerData.numberOfMessages));
635            }
636            return;
637        }
638        mBearerData = BearerData.decode(mEnvelope.bearerData);
639        if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
640            Rlog.d(LOG_TAG, "MT raw BearerData = '" +
641                      HexDump.toHexString(mEnvelope.bearerData) + "'");
642            Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
643        }
644        mMessageRef = mBearerData.messageId;
645        if (mBearerData.userData != null) {
646            mUserData = mBearerData.userData.payload;
647            mUserDataHeader = mBearerData.userData.userDataHeader;
648            mMessageBody = mBearerData.userData.payloadStr;
649        }
650
651        if (mOriginatingAddress != null) {
652            mOriginatingAddress.address = new String(mOriginatingAddress.origBytes);
653            if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
654                if (mOriginatingAddress.address.charAt(0) != '+') {
655                    mOriginatingAddress.address = "+" + mOriginatingAddress.address;
656                }
657            }
658            if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
659                    + mOriginatingAddress.address);
660        }
661
662        if (mBearerData.msgCenterTimeStamp != null) {
663            mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
664        }
665
666        if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
667
668        // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
669        if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
670            // The BearerData MsgStatus subparameter should only be
671            // included for DELIVERY_ACK messages.  If it occurred for
672            // other messages, it would be unclear what the status
673            // being reported refers to.  The MsgStatus subparameter
674            // is primarily useful to indicate error conditions -- a
675            // message without this subparameter is assumed to
676            // indicate successful delivery (status == 0).
677            if (! mBearerData.messageStatusSet) {
678                Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
679                        (mUserData == null ? "also missing" : "does have") +
680                        " userData).");
681                status = 0;
682            } else {
683                status = mBearerData.errorClass << 8;
684                status |= mBearerData.messageStatus;
685            }
686        } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
687            throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
688        }
689
690        if (mMessageBody != null) {
691            if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
692            parseMessageBody();
693        } else if ((mUserData != null) && VDBG) {
694            Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
695        }
696    }
697
698    /**
699     * Parses a broadcast SMS, possibly containing a CMAS alert.
700     */
701    public SmsCbMessage parseBroadcastSms() {
702        BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
703        if (bData == null) {
704            Rlog.w(LOG_TAG, "BearerData.decode() returned null");
705            return null;
706        }
707
708        if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
709            Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
710        }
711
712        String plmn = TelephonyManager.getDefault().getNetworkOperator();
713        SmsCbLocation location = new SmsCbLocation(plmn);
714
715        return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
716                SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
717                mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
718                bData.priority, null, bData.cmasWarningInfo);
719    }
720
721    /**
722     * {@inheritDoc}
723     */
724    @Override
725    public SmsConstants.MessageClass getMessageClass() {
726        if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
727            return SmsConstants.MessageClass.CLASS_0;
728        } else {
729            return SmsConstants.MessageClass.UNKNOWN;
730        }
731    }
732
733    /**
734     * Calculate the next message id, starting at 1 and iteratively
735     * incrementing within the range 1..65535 remembering the state
736     * via a persistent system property.  (See C.S0015-B, v2.0,
737     * 4.3.1.5) Since this routine is expected to be accessed via via
738     * binder-call, and hence should be thread-safe, it has been
739     * synchronized.
740     */
741    public synchronized static int getNextMessageId() {
742        // Testing and dialog with partners has indicated that
743        // msgId==0 is (sometimes?) treated specially by lower levels.
744        // Specifically, the ID is not preserved for delivery ACKs.
745        // Hence, avoid 0 -- constraining the range to 1..65535.
746        int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
747        String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
748        try{
749            SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
750            if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
751                Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
752                Rlog.d(LOG_TAG, "readback gets " +
753                        SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
754            }
755        } catch(RuntimeException ex) {
756            Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
757        }
758        return msgId;
759    }
760
761    /**
762     * Creates BearerData and Envelope from parameters for a Submit SMS.
763     * @return byte stream for SubmitPdu.
764     */
765    private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
766            UserData userData) {
767
768        /**
769         * TODO(cleanup): give this function a more meaningful name.
770         */
771
772        /**
773         * TODO(cleanup): Make returning null from the getSubmitPdu
774         * variations meaningful -- clean up the error feedback
775         * mechanism, and avoid null pointer exceptions.
776         */
777
778        /**
779         * North America Plus Code :
780         * Convert + code to 011 and dial out for international SMS
781         */
782        CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
783                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
784        if (destAddr == null) return null;
785
786        BearerData bearerData = new BearerData();
787        bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
788
789        bearerData.messageId = getNextMessageId();
790
791        bearerData.deliveryAckReq = statusReportRequested;
792        bearerData.userAckReq = false;
793        bearerData.readAckReq = false;
794        bearerData.reportReq = false;
795
796        bearerData.userData = userData;
797
798        byte[] encodedBearerData = BearerData.encode(bearerData);
799        if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
800            Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
801            Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
802        }
803        if (encodedBearerData == null) return null;
804
805        int teleservice = bearerData.hasUserDataHeader ?
806                SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
807
808        SmsEnvelope envelope = new SmsEnvelope();
809        envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
810        envelope.teleService = teleservice;
811        envelope.destAddress = destAddr;
812        envelope.bearerReply = RETURN_ACK;
813        envelope.bearerData = encodedBearerData;
814
815        /**
816         * TODO(cleanup): envelope looks to be a pointless class, get
817         * rid of it.  Also -- most of the envelope fields set here
818         * are ignored, why?
819         */
820
821        try {
822            /**
823             * TODO(cleanup): reference a spec and get rid of the ugly comments
824             */
825            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
826            DataOutputStream dos = new DataOutputStream(baos);
827            dos.writeInt(envelope.teleService);
828            dos.writeInt(0); //servicePresent
829            dos.writeInt(0); //serviceCategory
830            dos.write(destAddr.digitMode);
831            dos.write(destAddr.numberMode);
832            dos.write(destAddr.ton); // number_type
833            dos.write(destAddr.numberPlan);
834            dos.write(destAddr.numberOfDigits);
835            dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
836            // Subaddress is not supported.
837            dos.write(0); //subaddressType
838            dos.write(0); //subaddr_odd
839            dos.write(0); //subaddr_nbr_of_digits
840            dos.write(encodedBearerData.length);
841            dos.write(encodedBearerData, 0, encodedBearerData.length);
842            dos.close();
843
844            SubmitPdu pdu = new SubmitPdu();
845            pdu.encodedMessage = baos.toByteArray();
846            pdu.encodedScAddress = null;
847            return pdu;
848        } catch(IOException ex) {
849            Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
850        }
851        return null;
852    }
853
854    /**
855     * Creates byte array (pseudo pdu) from SMS object.
856     * Note: Do not call this method more than once per object!
857     * @hide
858     */
859    public void createPdu() {
860        SmsEnvelope env = mEnvelope;
861        CdmaSmsAddress addr = env.origAddress;
862        ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
863        DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
864
865        try {
866            dos.writeInt(env.messageType);
867            dos.writeInt(env.teleService);
868            dos.writeInt(env.serviceCategory);
869
870            dos.writeByte(addr.digitMode);
871            dos.writeByte(addr.numberMode);
872            dos.writeByte(addr.ton);
873            dos.writeByte(addr.numberPlan);
874            dos.writeByte(addr.numberOfDigits);
875            dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
876
877            dos.writeInt(env.bearerReply);
878            // CauseCode values:
879            dos.writeByte(env.replySeqNo);
880            dos.writeByte(env.errorClass);
881            dos.writeByte(env.causeCode);
882            //encoded BearerData:
883            dos.writeInt(env.bearerData.length);
884            dos.write(env.bearerData, 0, env.bearerData.length);
885            dos.close();
886
887            /**
888             * TODO(cleanup) -- The mPdu field is managed in
889             * a fragile manner, and it would be much nicer if
890             * accessing the serialized representation used a less
891             * fragile mechanism.  Maybe the getPdu method could
892             * generate a representation if there was not yet one?
893             */
894
895            mPdu = baos.toByteArray();
896        } catch (IOException ex) {
897            Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
898        }
899    }
900
901    /**
902     * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
903     * @hide
904     */
905    public static byte convertDtmfToAscii(byte dtmfDigit) {
906        byte asciiDigit;
907
908        switch (dtmfDigit) {
909        case  0: asciiDigit = 68; break; // 'D'
910        case  1: asciiDigit = 49; break; // '1'
911        case  2: asciiDigit = 50; break; // '2'
912        case  3: asciiDigit = 51; break; // '3'
913        case  4: asciiDigit = 52; break; // '4'
914        case  5: asciiDigit = 53; break; // '5'
915        case  6: asciiDigit = 54; break; // '6'
916        case  7: asciiDigit = 55; break; // '7'
917        case  8: asciiDigit = 56; break; // '8'
918        case  9: asciiDigit = 57; break; // '9'
919        case 10: asciiDigit = 48; break; // '0'
920        case 11: asciiDigit = 42; break; // '*'
921        case 12: asciiDigit = 35; break; // '#'
922        case 13: asciiDigit = 65; break; // 'A'
923        case 14: asciiDigit = 66; break; // 'B'
924        case 15: asciiDigit = 67; break; // 'C'
925        default:
926            asciiDigit = 32; // Invalid DTMF code
927            break;
928        }
929
930        return asciiDigit;
931    }
932
933    /** This function  shall be called to get the number of voicemails.
934     * @hide
935     */
936    public int getNumOfVoicemails() {
937        return mBearerData.numberOfMessages;
938    }
939
940    /**
941     * Returns a byte array that can be use to uniquely identify a received SMS message.
942     * C.S0015-B  4.3.1.6 Unique Message Identification.
943     *
944     * @return byte array uniquely identifying the message.
945     * @hide
946     */
947    public byte[] getIncomingSmsFingerprint() {
948        ByteArrayOutputStream output = new ByteArrayOutputStream();
949
950        output.write(mEnvelope.serviceCategory);
951        output.write(mEnvelope.teleService);
952        output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
953        output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
954        output.write(mEnvelope.origSubaddress.origBytes, 0,
955                mEnvelope.origSubaddress.origBytes.length);
956
957        return output.toByteArray();
958    }
959
960    /**
961     * Returns the list of service category program data, if present.
962     * @return a list of CdmaSmsCbProgramData objects, or null if not present
963     * @hide
964     */
965    public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
966        return mBearerData.serviceCategoryProgramData;
967    }
968}
969