1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.cdma;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.os.Build;
21 import android.sysprop.TelephonyProperties;
22 import android.telephony.PhoneNumberUtils;
23 import android.telephony.SmsCbLocation;
24 import android.telephony.SmsCbMessage;
25 import android.telephony.cdma.CdmaSmsCbProgramData;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
30 import com.android.internal.telephony.SmsAddress;
31 import com.android.internal.telephony.SmsConstants;
32 import com.android.internal.telephony.SmsHeader;
33 import com.android.internal.telephony.SmsMessageBase;
34 import com.android.internal.telephony.cdma.sms.BearerData;
35 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
36 import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
37 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
38 import com.android.internal.telephony.cdma.sms.UserData;
39 import com.android.internal.telephony.uicc.IccUtils;
40 import com.android.internal.util.BitwiseInputStream;
41 import com.android.internal.util.HexDump;
42 import com.android.telephony.Rlog;
43 
44 import java.io.BufferedOutputStream;
45 import java.io.ByteArrayInputStream;
46 import java.io.ByteArrayOutputStream;
47 import java.io.DataInputStream;
48 import java.io.DataOutputStream;
49 import java.io.IOException;
50 import java.util.ArrayList;
51 
52 /**
53  * TODO(cleanup): these constants are disturbing... are they not just
54  * different interpretations on one number?  And if we did not have
55  * terrible class name overlap, they would not need to be directly
56  * imported like this.  The class in this file could just as well be
57  * named CdmaSmsMessage, could it not?
58  */
59 
60 /**
61  * TODO(cleanup): internally returning null in many places makes
62  * debugging very hard (among many other reasons) and should be made
63  * more meaningful (replaced with exceptions for example).  Null
64  * returns should only occur at the very outside of the module/class
65  * scope.
66  */
67 
68 /**
69  * A Short Message Service message.
70  *
71  */
72 public class SmsMessage extends SmsMessageBase {
73     static final String LOG_TAG = "SmsMessage";
74     static private final String LOGGABLE_TAG = "CDMA:SMS";
75     private static final boolean VDBG = false;
76 
77     private final static byte TELESERVICE_IDENTIFIER                    = 0x00;
78     private final static byte SERVICE_CATEGORY                          = 0x01;
79     private final static byte ORIGINATING_ADDRESS                       = 0x02;
80     private final static byte ORIGINATING_SUB_ADDRESS                   = 0x03;
81     private final static byte DESTINATION_ADDRESS                       = 0x04;
82     private final static byte DESTINATION_SUB_ADDRESS                   = 0x05;
83     private final static byte BEARER_REPLY_OPTION                       = 0x06;
84     private final static byte CAUSE_CODES                               = 0x07;
85     private final static byte BEARER_DATA                               = 0x08;
86 
87     /**
88      *  Status of a previously submitted SMS.
89      *  This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
90      *  Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
91      *  See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
92      */
93     private int status;
94 
95     /** Specifies if a return of an acknowledgment is requested for send SMS */
96     private static final int RETURN_NO_ACK  = 0;
97     private static final int RETURN_ACK     = 1;
98 
99     /**
100      * Supported priority modes for CDMA SMS messages
101      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
102      */
103     private static final int PRIORITY_NORMAL        = 0x0;
104     private static final int PRIORITY_INTERACTIVE   = 0x1;
105     private static final int PRIORITY_URGENT        = 0x2;
106     private static final int PRIORITY_EMERGENCY     = 0x3;
107 
108     @UnsupportedAppUsage
109     private SmsEnvelope mEnvelope;
110     @UnsupportedAppUsage
111     private BearerData mBearerData;
112 
113     /** @hide */
SmsMessage(SmsAddress addr, SmsEnvelope env)114     public SmsMessage(SmsAddress addr, SmsEnvelope env) {
115         mOriginatingAddress = addr;
116         mEnvelope = env;
117         createPdu();
118     }
119 
120     @UnsupportedAppUsage
SmsMessage()121     public SmsMessage() {}
122 
123     public static class SubmitPdu extends SubmitPduBase {
124         @UnsupportedAppUsage
SubmitPdu()125         public SubmitPdu() {
126         }
127     }
128 
129     /**
130      * Create an SmsMessage from a raw PDU.
131      * Note: In CDMA the PDU is just a byte representation of the received Sms.
132      */
133     @UnsupportedAppUsage
createFromPdu(byte[] pdu)134     public static SmsMessage createFromPdu(byte[] pdu) {
135         SmsMessage msg = new SmsMessage();
136 
137         try {
138             msg.parsePdu(pdu);
139             return msg;
140         } catch (RuntimeException ex) {
141             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
142             return null;
143         } catch (OutOfMemoryError e) {
144             Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
145             return null;
146         }
147     }
148 
149     /**
150      * Creates an SmsMessage from an SMS EF record.
151      *
152      * @param index Index of SMS EF record.
153      * @param data Record data.
154      * @return An SmsMessage representing the record.
155      *
156      * @hide
157      */
158     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
159             + "android.telephony.SmsMessage} API instead")
createFromEfRecord(int index, byte[] data)160     public static SmsMessage createFromEfRecord(int index, byte[] data) {
161         try {
162             SmsMessage msg = new SmsMessage();
163 
164             msg.mIndexOnIcc = index;
165 
166             // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
167             // or STORED_UNSENT
168             // See 3GPP2 C.S0023 3.4.27
169             if ((data[0] & 1) == 0) {
170                 Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
171                 return null;
172             } else {
173                 msg.mStatusOnIcc = data[0] & 0x07;
174             }
175 
176             // Second byte is the MSG_LEN, length of the message
177             // See 3GPP2 C.S0023 3.4.27
178             int size = data[1] & 0xFF;
179 
180             // Note: Data may include trailing FF's.  That's OK; message
181             // should still parse correctly.
182             byte[] pdu = new byte[size];
183             System.arraycopy(data, 2, pdu, 0, size);
184             // the message has to be parsed before it can be displayed
185             // see gsm.SmsMessage
186             msg.parsePduFromEfRecord(pdu);
187             return msg;
188         } catch (RuntimeException ex) {
189             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
190             return null;
191         }
192 
193     }
194 
195     /**
196      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
197      */
getTPLayerLengthForPDU(String pdu)198     public static int getTPLayerLengthForPDU(String pdu) {
199         Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
200         return 0;
201     }
202 
203     /**
204      * Gets an SMS-SUBMIT PDU for a destination address and a message.
205      *
206      * @param scAddr Service Centre address. No use for this message.
207      * @param destAddr the address of the destination for the message.
208      * @param message string representation of the message payload.
209      * @param statusReportRequested indicates whether a report is requested for this message.
210      * @param smsHeader array containing the data for the User Data Header, preceded by the Element
211      *                  Identifiers.
212      * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
213      *         null on encode error.
214      * @hide
215      */
216     @UnsupportedAppUsage
getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader)217     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
218             boolean statusReportRequested, SmsHeader smsHeader) {
219         return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1);
220     }
221 
222     /**
223      * Gets an SMS-SUBMIT PDU for a destination address and a message.
224      *
225      * @param scAddr Service Centre address. No use for this message.
226      * @param destAddr the address of the destination for the message.
227      * @param message string representation of the message payload.
228      * @param statusReportRequested indicates whether a report is requested for this message.
229      * @param smsHeader array containing the data for the User Data Header, preceded by the Element
230      *                  Identifiers.
231      * @param priority priority level of the message.
232      * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
233      *         null on encode error.
234      * @hide
235      */
236     @UnsupportedAppUsage
getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority)237     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
238             boolean statusReportRequested, SmsHeader smsHeader, int priority) {
239 
240         /**
241          * TODO(cleanup): Do we really want silent failure like this?
242          * Would it not be much more reasonable to make sure we don't
243          * call this function if we really want nothing done?
244          */
245         if (message == null || destAddr == null) {
246             return null;
247         }
248 
249         UserData uData = new UserData();
250         uData.payloadStr = message;
251         uData.userDataHeader = smsHeader;
252         return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority);
253     }
254 
255     /**
256      * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
257      *
258      * @param scAddr Service Centre address. No use for this message.
259      * @param destAddr the address of the destination for the message.
260      * @param destPort the port to deliver the message to at the destination.
261      * @param data the data for the message.
262      * @param statusReportRequested indicates whether a report is requested for this message.
263      * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
264      *         null on encode error.
265      */
266     @UnsupportedAppUsage
getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] data, boolean statusReportRequested)267     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
268             byte[] data, boolean statusReportRequested) {
269 
270         /**
271          * TODO(cleanup): this is not a general-purpose SMS creation
272          * method, but rather something specialized to messages
273          * containing OCTET encoded (meaning non-human-readable) user
274          * data.  The name should reflect that, and not just overload.
275          */
276 
277         SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
278         portAddrs.destPort = destPort;
279         portAddrs.origPort = 0;
280         portAddrs.areEightBits = false;
281 
282         SmsHeader smsHeader = new SmsHeader();
283         smsHeader.portAddrs = portAddrs;
284 
285         UserData uData = new UserData();
286         uData.userDataHeader = smsHeader;
287         uData.msgEncoding = UserData.ENCODING_OCTET;
288         uData.msgEncodingSet = true;
289         uData.payload = data;
290 
291         return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
292     }
293 
294     /**
295      * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
296      *
297      * @param destAddr the address of the destination for the message.
298      * @param userData the data for the message.
299      * @param statusReportRequested indicates whether a report is requested for this message.
300      * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
301      *         null on encode error.
302      */
303     @UnsupportedAppUsage
getSubmitPdu(String destAddr, UserData userData, boolean statusReportRequested)304     public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
305             boolean statusReportRequested) {
306         return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
307     }
308 
309     /**
310      * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
311      *
312      * @param destAddr the address of the destination for the message.
313      * @param userData the data for the message.
314      * @param statusReportRequested indicates whether a report is requested for this message.
315      * @param priority Priority level of the message.
316      * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
317      *         null on encode error.
318      */
319     @UnsupportedAppUsage
getSubmitPdu(String destAddr, UserData userData, boolean statusReportRequested, int priority)320     public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
321             boolean statusReportRequested, int priority) {
322         return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority);
323     }
324 
325     /**
326      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
327      */
328     @Override
getProtocolIdentifier()329     public int getProtocolIdentifier() {
330         Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
331         // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
332         return 0;
333     }
334 
335     /**
336      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
337      */
338     @Override
isReplace()339     public boolean isReplace() {
340         Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
341         return false;
342     }
343 
344     /**
345      * {@inheritDoc}
346      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
347      */
348     @Override
isCphsMwiMessage()349     public boolean isCphsMwiMessage() {
350         Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
351         return false;
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     @Override
isMWIClearMessage()358     public boolean isMWIClearMessage() {
359         return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
360     }
361 
362     /**
363      * {@inheritDoc}
364      */
365     @Override
isMWISetMessage()366     public boolean isMWISetMessage() {
367         return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
368     }
369 
370     /**
371      * {@inheritDoc}
372      */
373     @Override
isMwiDontStore()374     public boolean isMwiDontStore() {
375         return ((mBearerData != null) &&
376                 (mBearerData.numberOfMessages > 0) &&
377                 (mBearerData.userData == null));
378     }
379 
380     /**
381      * Returns the status for a previously submitted message.
382      * For not interfering with status codes from GSM, this status code is
383      * shifted to the bits 31-16.
384      */
385     @Override
getStatus()386     public int getStatus() {
387         return (status << 16);
388     }
389 
390     /** Return true iff the bearer data message type is DELIVERY_ACK. */
391     @UnsupportedAppUsage
392     @Override
isStatusReportMessage()393     public boolean isStatusReportMessage() {
394         return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
395     }
396 
397     /**
398      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
399      */
400     @Override
isReplyPathPresent()401     public boolean isReplyPathPresent() {
402         Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
403         return false;
404     }
405 
406     /**
407      * Calculate the number of septets needed to encode the message.
408      *
409      * @param messageBody the message to encode
410      * @param use7bitOnly ignore (but still count) illegal characters if true
411      * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
412      * @return TextEncodingDetails
413      */
414     @UnsupportedAppUsage
calculateLength(CharSequence messageBody, boolean use7bitOnly, boolean isEntireMsg)415     public static TextEncodingDetails calculateLength(CharSequence messageBody,
416             boolean use7bitOnly, boolean isEntireMsg) {
417         return BearerData.calcTextEncodingDetails(messageBody, use7bitOnly, isEntireMsg);
418     }
419 
420     /**
421      * Returns the teleservice type of the message.
422      * @return the teleservice:
423      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
424      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
425      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
426      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
427      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
428     */
429     @UnsupportedAppUsage
getTeleService()430     public int getTeleService() {
431         return mEnvelope.teleService;
432     }
433 
434     /**
435      * Returns the message type of the message.
436      * @return the message type:
437      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
438      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
439      *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
440     */
441     @UnsupportedAppUsage
getMessageType()442     public int getMessageType() {
443         // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
444         // Use the service category parameter to detect CMAS and other cell broadcast messages.
445         if (mEnvelope.serviceCategory != 0) {
446             return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
447         } else {
448             return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
449         }
450     }
451 
452     /**
453      * Decodes pdu to an empty SMS object.
454      * In the CDMA case the pdu is just an internal byte stream representation
455      * of the SMS Java-object.
456      * @see #createPdu()
457      */
parsePdu(byte[] pdu)458     private void parsePdu(byte[] pdu) {
459         ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
460         DataInputStream dis = new DataInputStream(bais);
461         int length;
462         int bearerDataLength;
463         SmsEnvelope env = new SmsEnvelope();
464         CdmaSmsAddress addr = new CdmaSmsAddress();
465         // We currently do not parse subaddress in PDU, but it is required when determining
466         // fingerprint (see getIncomingSmsFingerprint()).
467         CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress();
468 
469         try {
470             env.messageType = dis.readInt();
471             env.teleService = dis.readInt();
472             env.serviceCategory = dis.readInt();
473 
474             addr.digitMode = dis.readByte();
475             addr.numberMode = dis.readByte();
476             addr.ton = dis.readByte();
477             addr.numberPlan = dis.readByte();
478 
479             length = dis.readUnsignedByte();
480             addr.numberOfDigits = length;
481 
482             // Correctness check on the length
483             if (length > pdu.length) {
484                 throw new RuntimeException(
485                         "createFromPdu: Invalid pdu, addr.numberOfDigits " + length
486                         + " > pdu len " + pdu.length);
487             }
488             addr.origBytes = new byte[length];
489             dis.read(addr.origBytes, 0, length); // digits
490 
491             env.bearerReply = dis.readInt();
492             // CauseCode values:
493             env.replySeqNo = dis.readByte();
494             env.errorClass = dis.readByte();
495             env.causeCode = dis.readByte();
496 
497             //encoded BearerData:
498             bearerDataLength = dis.readInt();
499             // Correctness check on the length
500             if (bearerDataLength > pdu.length) {
501                 throw new RuntimeException(
502                         "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
503                         + " > pdu len " + pdu.length);
504             }
505             env.bearerData = new byte[bearerDataLength];
506             dis.read(env.bearerData, 0, bearerDataLength);
507             dis.close();
508         } catch (IOException ex) {
509             throw new RuntimeException(
510                     "createFromPdu: conversion from byte array to object failed: " + ex, ex);
511         } catch (Exception ex) {
512             Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
513         }
514 
515         // link the filled objects to this SMS
516         mOriginatingAddress = addr;
517         env.origAddress = addr;
518         env.origSubaddress = subaddr;
519         mEnvelope = env;
520         mPdu = pdu;
521 
522         parseSms();
523     }
524 
525     /**
526      * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
527      */
parsePduFromEfRecord(byte[] pdu)528     private void parsePduFromEfRecord(byte[] pdu) {
529         ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
530         DataInputStream dis = new DataInputStream(bais);
531         SmsEnvelope env = new SmsEnvelope();
532         CdmaSmsAddress addr = new CdmaSmsAddress();
533         CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
534 
535         try {
536             env.messageType = dis.readByte();
537 
538             while (dis.available() > 0) {
539                 int parameterId = dis.readByte();
540                 int parameterLen = dis.readUnsignedByte();
541                 byte[] parameterData = new byte[parameterLen];
542 
543                 switch (parameterId) {
544                     case TELESERVICE_IDENTIFIER:
545                         /*
546                          * 16 bit parameter that identifies which upper layer
547                          * service access point is sending or should receive
548                          * this message
549                          */
550                         env.teleService = dis.readUnsignedShort();
551                         Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
552                         break;
553                     case SERVICE_CATEGORY:
554                         /*
555                          * 16 bit parameter that identifies type of service as
556                          * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
557                          */
558                         env.serviceCategory = dis.readUnsignedShort();
559                         break;
560                     case ORIGINATING_ADDRESS:
561                     case DESTINATION_ADDRESS:
562                         dis.read(parameterData, 0, parameterLen);
563                         BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
564                         addr.digitMode = addrBis.read(1);
565                         addr.numberMode = addrBis.read(1);
566                         int numberType = 0;
567                         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
568                             numberType = addrBis.read(3);
569                             addr.ton = numberType;
570 
571                             if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
572                                 addr.numberPlan = addrBis.read(4);
573                         }
574 
575                         addr.numberOfDigits = addrBis.read(8);
576 
577                         byte[] data = new byte[addr.numberOfDigits];
578                         byte b = 0x00;
579 
580                         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
581                             /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
582                             for (int index = 0; index < addr.numberOfDigits; index++) {
583                                 b = (byte) (0xF & addrBis.read(4));
584                                 // convert the value if it is 4-bit DTMF to 8
585                                 // bit
586                                 data[index] = convertDtmfToAscii(b);
587                             }
588                         } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
589                             if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
590                                 for (int index = 0; index < addr.numberOfDigits; index++) {
591                                     b = (byte) (0xFF & addrBis.read(8));
592                                     data[index] = b;
593                                 }
594 
595                             } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
596                                 if (numberType == 2)
597                                     Rlog.e(LOG_TAG, "TODO: Addr is email id");
598                                 else
599                                     Rlog.e(LOG_TAG,
600                                           "TODO: Addr is data network address");
601                             } else {
602                                 Rlog.e(LOG_TAG, "Addr is of incorrect type");
603                             }
604                         } else {
605                             Rlog.e(LOG_TAG, "Incorrect Digit mode");
606                         }
607                         addr.origBytes = data;
608                         Rlog.pii(LOG_TAG, "Addr=" + addr.toString());
609                         if (parameterId == ORIGINATING_ADDRESS) {
610                             env.origAddress = addr;
611                             mOriginatingAddress = addr;
612                         } else {
613                             env.destAddress = addr;
614                             mRecipientAddress = addr;
615                         }
616                         break;
617                     case ORIGINATING_SUB_ADDRESS:
618                     case DESTINATION_SUB_ADDRESS:
619                         dis.read(parameterData, 0, parameterLen);
620                         BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
621                         subAddr.type = subAddrBis.read(3);
622                         subAddr.odd = subAddrBis.readByteArray(1)[0];
623                         int subAddrLen = subAddrBis.read(8);
624                         byte[] subdata = new byte[subAddrLen];
625                         for (int index = 0; index < subAddrLen; index++) {
626                             b = (byte) (0xFF & subAddrBis.read(4));
627                             // convert the value if it is 4-bit DTMF to 8 bit
628                             subdata[index] = convertDtmfToAscii(b);
629                         }
630                         subAddr.origBytes = subdata;
631                         if (parameterId == ORIGINATING_SUB_ADDRESS) {
632                             env.origSubaddress = subAddr;
633                         } else {
634                             env.destSubaddress = subAddr;
635                         }
636                         break;
637                     case BEARER_REPLY_OPTION:
638                         dis.read(parameterData, 0, parameterLen);
639                         BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
640                         env.bearerReply = replyOptBis.read(6);
641                         break;
642                     case CAUSE_CODES:
643                         dis.read(parameterData, 0, parameterLen);
644                         BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
645                         env.replySeqNo = ccBis.readByteArray(6)[0];
646                         env.errorClass = ccBis.readByteArray(2)[0];
647                         if (env.errorClass != 0x00)
648                             env.causeCode = ccBis.readByteArray(8)[0];
649                         break;
650                     case BEARER_DATA:
651                         dis.read(parameterData, 0, parameterLen);
652                         env.bearerData = parameterData;
653                         break;
654                     default:
655                         throw new Exception("unsupported parameterId (" + parameterId + ")");
656                 }
657             }
658             bais.close();
659             dis.close();
660         } catch (Exception ex) {
661             Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
662         }
663 
664         // link the filled objects to this SMS
665         mEnvelope = env;
666         mPdu = pdu;
667 
668         parseSms();
669     }
670 
671     /**
672      * Pre-processes an SMS WAP for Teleservice Id 0xFDEA(65002).
673      *
674      * It requires an additional header parsing to extract new Message Identifier and new User Data
675      * from WDP SMS User Data.
676      *
677      * - WDP SMS User Data Subparameter =
678      *   |User Data SUBPARAMETER_ID ~ NUM_FIELDS| + |CHARi| + |RESERVED|
679      *
680      * - WDP SMS User Data Subparameter CHARi =
681      *   |New Message Identifier Subparameter(HEADER_IND = 0)| +
682      *   |New User Data Subparameter(MSG_ENCODING = ENCODING_OCTET)|
683      *
684      * @return true if preprocessing is successful, false otherwise.
685      */
preprocessCdmaFdeaWap()686     public boolean preprocessCdmaFdeaWap() {
687         try {
688             BitwiseInputStream inStream = new BitwiseInputStream(mUserData);
689 
690             // Message Identifier SUBPARAMETER_ID(0x00)
691             if (inStream.read(8) != 0x00) {
692                 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAMETER_ID");
693                 return false;
694             }
695 
696             // Message Identifier SUBPARAM_LEN(0x03)
697             if (inStream.read(8) != 0x03) {
698                 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAM_LEN");
699                 return false;
700             }
701 
702             // Message Identifier MESSAGE_TYPE
703             mBearerData.messageType = inStream.read(4);
704 
705             // Message Identifier MESSAGE_ID
706             int msgId = inStream.read(8) << 8;
707             msgId |= inStream.read(8);
708             mBearerData.messageId = msgId;
709             mMessageRef = msgId;
710 
711             // Message Identifier HEADER_IND
712             mBearerData.hasUserDataHeader = (inStream.read(1) == 1);
713             if (mBearerData.hasUserDataHeader) {
714                 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier HEADER_IND");
715                 return false;
716             }
717 
718             // Message Identifier RESERVED
719             inStream.skip(3);
720 
721             // User Data SUBPARAMETER_ID(0x01)
722             if (inStream.read(8) != 0x01) {
723                 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data SUBPARAMETER_ID");
724                 return false;
725             }
726 
727             // User Data SUBPARAM_LEN
728             int userDataLen = inStream.read(8) * 8;
729 
730             // User Data MSG_ENCODING
731             mBearerData.userData.msgEncoding = inStream.read(5);
732             int consumedBits = 5;
733             if (mBearerData.userData.msgEncoding != UserData.ENCODING_OCTET) {
734                 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data MSG_ENCODING");
735                 return false;
736             }
737 
738             // User Data NUM_FIELDS
739             mBearerData.userData.numFields = inStream.read(8);
740             consumedBits += 8;
741 
742             int remainingBits = userDataLen - consumedBits;
743             int dataBits = mBearerData.userData.numFields * 8;
744             dataBits = dataBits < remainingBits ? dataBits : remainingBits;
745             mBearerData.userData.payload = inStream.readByteArray(dataBits);
746             mUserData = mBearerData.userData.payload;
747             return true;
748         } catch (BitwiseInputStream.AccessException ex) {
749             Rlog.e(LOG_TAG, "Fail to preprocess FDEA WAP: " + ex);
750         }
751         return false;
752     }
753 
754     /**
755      * Parses a SMS message from its BearerData stream.
756      */
757     @UnsupportedAppUsage
758     public void parseSms() {
759         // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
760         // It contains only an 8-bit number with the number of messages waiting
761         if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
762             mBearerData = new BearerData();
763             if (mEnvelope.bearerData != null) {
764                 mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
765             }
766             if (VDBG) {
767                 Rlog.d(LOG_TAG, "parseSms: get MWI " +
768                       Integer.toString(mBearerData.numberOfMessages));
769             }
770             return;
771         }
772         mBearerData = BearerData.decode(mEnvelope.bearerData);
773         if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
774             Rlog.d(LOG_TAG, "MT raw BearerData = '" +
775                       HexDump.toHexString(mEnvelope.bearerData) + "'");
776             Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
777         }
778         mMessageRef = mBearerData.messageId;
779         if (mBearerData.userData != null) {
780             mUserData = mBearerData.userData.payload;
781             mUserDataHeader = mBearerData.userData.userDataHeader;
782             mMessageBody = mBearerData.userData.payloadStr;
783         }
784 
785         if (mOriginatingAddress != null) {
786             decodeSmsDisplayAddress(mOriginatingAddress);
787             if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " + mOriginatingAddress.address);
788         }
789 
790         if (mRecipientAddress != null) {
791             decodeSmsDisplayAddress(mRecipientAddress);
792             if (VDBG) Rlog.v(LOG_TAG, "SMS destination address: " + mRecipientAddress.address);
793         }
794 
795         if (mBearerData.msgCenterTimeStamp != null) {
796             mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis();
797         }
798 
799         if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
800 
801         // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
802         if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
803             // The BearerData MsgStatus subparameter should only be
804             // included for DELIVERY_ACK messages.  If it occurred for
805             // other messages, it would be unclear what the status
806             // being reported refers to.  The MsgStatus subparameter
807             // is primarily useful to indicate error conditions -- a
808             // message without this subparameter is assumed to
809             // indicate successful delivery.
810             if (!mBearerData.messageStatusSet) {
811                 Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
812                         (mUserData == null ? "also missing" : "does have") +
813                         " userData).");
814                 status = (BearerData.ERROR_NONE << 8) | BearerData.STATUS_DELIVERED;
815             } else {
816                 status = mBearerData.errorClass << 8;
817                 status |= mBearerData.messageStatus;
818             }
819         } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER
820                 && mBearerData.messageType != BearerData.MESSAGE_TYPE_SUBMIT) {
821             throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
822         }
823 
824         if (mMessageBody != null) {
825             if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
826             parseMessageBody();
827         } else if ((mUserData != null) && VDBG) {
828             Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
829         }
830     }
831 
832     private void decodeSmsDisplayAddress(SmsAddress addr) {
833         // PCD(Plus Code Dialing)
834         // 1) Replaces IDD(International Direct Dialing) with the '+' if address starts with it.
835         // TODO: Skip it for EF SMS(SUBMIT and DELIVER) because the IDD depends on current network?
836         // 2) Adds the '+' prefix if TON is International
837         // 3) Keeps the '+' if address starts with the '+'
838         String idd = TelephonyProperties.operator_idp_string().orElse(null);
839         addr.address = new String(addr.origBytes);
840         if (!TextUtils.isEmpty(idd) && addr.address.startsWith(idd)) {
841             addr.address = "+" + addr.address.substring(idd.length());
842         } else if (addr.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
843             if (addr.address.charAt(0) != '+') {
844                 addr.address = "+" + addr.address;
845             }
846         }
847         Rlog.pii(LOG_TAG, " decodeSmsDisplayAddress = " + addr.address);
848     }
849 
850     /**
851      * Parses a broadcast SMS, possibly containing a CMAS alert.
852      *
853      * @param plmn the PLMN for a broadcast SMS
854      * @param slotIndex SIM slot index
855      * @param subId Subscription id
856      */
857     public SmsCbMessage parseBroadcastSms(String plmn, int slotIndex, int subId) {
858         BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
859         if (bData == null) {
860             Rlog.w(LOG_TAG, "BearerData.decode() returned null");
861             return null;
862         }
863 
864         if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
865             Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
866         }
867 
868         SmsCbLocation location = new SmsCbLocation(plmn);
869 
870         return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
871                 SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
872                 mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
873                 bData.priority, null, bData.cmasWarningInfo, slotIndex, subId);
874     }
875 
876     /**
877      * @return the bearer data byte array
878      */
879     public byte[] getEnvelopeBearerData() {
880         return mEnvelope.bearerData;
881     }
882 
883     /**
884      * @return the 16-bit CDMA SCPT service category
885      */
886     public @CdmaSmsCbProgramData.Category int getEnvelopeServiceCategory() {
887         return mEnvelope.serviceCategory;
888     }
889 
890     /**
891      * {@inheritDoc}
892      */
893     @Override
894     public SmsConstants.MessageClass getMessageClass() {
895         if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
896             return SmsConstants.MessageClass.CLASS_0;
897         } else {
898             return SmsConstants.MessageClass.UNKNOWN;
899         }
900     }
901 
902     /**
903      * Calculate the next message id, starting at 1 and iteratively
904      * incrementing within the range 1..65535 remembering the state
905      * via a persistent system property.  (See C.S0015-B, v2.0,
906      * 4.3.1.5) Since this routine is expected to be accessed via via
907      * binder-call, and hence should be thread-safe, it has been
908      * synchronized.
909      */
910     @UnsupportedAppUsage
911     public synchronized static int getNextMessageId() {
912         // Testing and dialog with partners has indicated that
913         // msgId==0 is (sometimes?) treated specially by lower levels.
914         // Specifically, the ID is not preserved for delivery ACKs.
915         // Hence, avoid 0 -- constraining the range to 1..65535.
916         int msgId = TelephonyProperties.cdma_msg_id().orElse(1);
917         int nextMsgId = msgId % 0xFFFF + 1;
918         try{
919             TelephonyProperties.cdma_msg_id(nextMsgId);
920             if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
921                 Rlog.d(LOG_TAG, "next persist.radio.cdma.msgid = " + nextMsgId);
922                 Rlog.d(LOG_TAG, "readback gets " + TelephonyProperties.cdma_msg_id().orElse(1));
923             }
924         } catch(RuntimeException ex) {
925             Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
926         }
927         return msgId;
928     }
929 
930     /**
931      * Creates BearerData and Envelope from parameters for a Submit SMS.
932      * @return byte stream for SubmitPdu.
933      */
934     @UnsupportedAppUsage
935     private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
936             UserData userData) {
937         return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1);
938     }
939 
940     /**
941      * Creates BearerData and Envelope from parameters for a Submit SMS.
942      * @return byte stream for SubmitPdu.
943      */
944     private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
945             UserData userData, int priority) {
946 
947         /**
948          * TODO(cleanup): give this function a more meaningful name.
949          */
950 
951         /**
952          * TODO(cleanup): Make returning null from the getSubmitPdu
953          * variations meaningful -- clean up the error feedback
954          * mechanism, and avoid null pointer exceptions.
955          */
956 
957         /**
958          * North America Plus Code :
959          * Convert + code to 011 and dial out for international SMS
960          */
961         CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
962                 PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
963         if (destAddr == null) return null;
964 
965         BearerData bearerData = new BearerData();
966         bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
967 
968         bearerData.messageId = getNextMessageId();
969 
970         bearerData.deliveryAckReq = statusReportRequested;
971         bearerData.userAckReq = false;
972         bearerData.readAckReq = false;
973         bearerData.reportReq = false;
974         if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) {
975             bearerData.priorityIndicatorSet = true;
976             bearerData.priority = priority;
977         }
978 
979         bearerData.userData = userData;
980 
981         byte[] encodedBearerData = BearerData.encode(bearerData);
982         if (encodedBearerData == null) return null;
983         if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
984             Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
985             Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
986         }
987 
988         int teleservice = (bearerData.hasUserDataHeader
989                 && userData.msgEncoding != UserData.ENCODING_7BIT_ASCII)
990                 ? SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
991 
992         SmsEnvelope envelope = new SmsEnvelope();
993         envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
994         envelope.teleService = teleservice;
995         envelope.destAddress = destAddr;
996         envelope.bearerReply = RETURN_ACK;
997         envelope.bearerData = encodedBearerData;
998 
999         /**
1000          * TODO(cleanup): envelope looks to be a pointless class, get
1001          * rid of it.  Also -- most of the envelope fields set here
1002          * are ignored, why?
1003          */
1004 
1005         try {
1006             /**
1007              * TODO(cleanup): reference a spec and get rid of the ugly comments
1008              */
1009             ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
1010             DataOutputStream dos = new DataOutputStream(baos);
1011             dos.writeInt(envelope.teleService);
1012             dos.writeInt(0); //servicePresent
1013             dos.writeInt(0); //serviceCategory
1014             dos.write(destAddr.digitMode);
1015             dos.write(destAddr.numberMode);
1016             dos.write(destAddr.ton); // number_type
1017             dos.write(destAddr.numberPlan);
1018             dos.write(destAddr.numberOfDigits);
1019             dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
1020             // Subaddress is not supported.
1021             dos.write(0); //subaddressType
1022             dos.write(0); //subaddr_odd
1023             dos.write(0); //subaddr_nbr_of_digits
1024             dos.write(encodedBearerData.length);
1025             dos.write(encodedBearerData, 0, encodedBearerData.length);
1026             dos.close();
1027 
1028             SubmitPdu pdu = new SubmitPdu();
1029             pdu.encodedMessage = baos.toByteArray();
1030             pdu.encodedScAddress = null;
1031             return pdu;
1032         } catch(IOException ex) {
1033             Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
1034         }
1035         return null;
1036     }
1037 
1038     /**
1039      * Gets an SMS-DELIVER PDU for a originating address and a message.
1040      *
1041      * @param origAddr the address of the originating for the message.
1042      * @param message string representation of the message payload.
1043      * @param date the time stamp the message was received.
1044      * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
1045      *         null on encode error.
1046      * @hide
1047      */
1048     public static SubmitPdu getDeliverPdu(String origAddr, String message, long date) {
1049         if (origAddr == null || message == null) {
1050             return null;
1051         }
1052 
1053         CdmaSmsAddress addr = CdmaSmsAddress.parse(origAddr);
1054         if (addr == null) return null;
1055 
1056         BearerData bearerData = new BearerData();
1057         bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
1058 
1059         bearerData.messageId = 0;
1060 
1061         bearerData.deliveryAckReq = false;
1062         bearerData.userAckReq = false;
1063         bearerData.readAckReq = false;
1064         bearerData.reportReq = false;
1065 
1066         bearerData.userData = new UserData();
1067         bearerData.userData.payloadStr = message;
1068 
1069         bearerData.msgCenterTimeStamp = BearerData.TimeStamp.fromMillis(date);
1070 
1071         byte[] encodedBearerData = BearerData.encode(bearerData);
1072         if (encodedBearerData == null) return null;
1073 
1074         try {
1075             ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
1076             DataOutputStream dos = new DataOutputStream(baos);
1077             dos.writeInt(SmsEnvelope.TELESERVICE_WMT);
1078             dos.writeInt(0); // servicePresent
1079             dos.writeInt(0); // serviceCategory
1080             dos.write(addr.digitMode);
1081             dos.write(addr.numberMode);
1082             dos.write(addr.ton); // number_type
1083             dos.write(addr.numberPlan);
1084             dos.write(addr.numberOfDigits);
1085             dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
1086             // Subaddress is not supported.
1087             dos.write(0); // subaddressType
1088             dos.write(0); // subaddr_odd
1089             dos.write(0); // subaddr_nbr_of_digits
1090             dos.write(encodedBearerData.length);
1091             dos.write(encodedBearerData, 0, encodedBearerData.length);
1092             dos.close();
1093 
1094             SubmitPdu pdu = new SubmitPdu();
1095             pdu.encodedMessage = baos.toByteArray();
1096             pdu.encodedScAddress = null;
1097             return pdu;
1098         } catch (IOException ex) {
1099             Rlog.e(LOG_TAG, "creating Deliver PDU failed: " + ex);
1100         }
1101         return null;
1102     }
1103 
1104     /**
1105      * Creates byte array (pseudo pdu) from SMS object.
1106      * Note: Do not call this method more than once per object!
1107      * @hide
1108      */
1109     public void createPdu() {
1110         SmsEnvelope env = mEnvelope;
1111         CdmaSmsAddress addr = env.origAddress;
1112         ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
1113         DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
1114 
1115         try {
1116             dos.writeInt(env.messageType);
1117             dos.writeInt(env.teleService);
1118             dos.writeInt(env.serviceCategory);
1119 
1120             dos.writeByte(addr.digitMode);
1121             dos.writeByte(addr.numberMode);
1122             dos.writeByte(addr.ton);
1123             dos.writeByte(addr.numberPlan);
1124             dos.writeByte(addr.numberOfDigits);
1125             dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
1126 
1127             dos.writeInt(env.bearerReply);
1128             // CauseCode values:
1129             dos.writeByte(env.replySeqNo);
1130             dos.writeByte(env.errorClass);
1131             dos.writeByte(env.causeCode);
1132             //encoded BearerData:
1133             dos.writeInt(env.bearerData.length);
1134             dos.write(env.bearerData, 0, env.bearerData.length);
1135             dos.close();
1136 
1137             /**
1138              * TODO(cleanup) -- The mPdu field is managed in
1139              * a fragile manner, and it would be much nicer if
1140              * accessing the serialized representation used a less
1141              * fragile mechanism.  Maybe the getPdu method could
1142              * generate a representation if there was not yet one?
1143              */
1144 
1145             mPdu = baos.toByteArray();
1146         } catch (IOException ex) {
1147             Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
1148         }
1149     }
1150 
1151     /**
1152      * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
1153      * @hide
1154      */
1155     public static byte convertDtmfToAscii(byte dtmfDigit) {
1156         byte asciiDigit;
1157 
1158         switch (dtmfDigit) {
1159         case  0: asciiDigit = 68; break; // 'D'
1160         case  1: asciiDigit = 49; break; // '1'
1161         case  2: asciiDigit = 50; break; // '2'
1162         case  3: asciiDigit = 51; break; // '3'
1163         case  4: asciiDigit = 52; break; // '4'
1164         case  5: asciiDigit = 53; break; // '5'
1165         case  6: asciiDigit = 54; break; // '6'
1166         case  7: asciiDigit = 55; break; // '7'
1167         case  8: asciiDigit = 56; break; // '8'
1168         case  9: asciiDigit = 57; break; // '9'
1169         case 10: asciiDigit = 48; break; // '0'
1170         case 11: asciiDigit = 42; break; // '*'
1171         case 12: asciiDigit = 35; break; // '#'
1172         case 13: asciiDigit = 65; break; // 'A'
1173         case 14: asciiDigit = 66; break; // 'B'
1174         case 15: asciiDigit = 67; break; // 'C'
1175         default:
1176             asciiDigit = 32; // Invalid DTMF code
1177             break;
1178         }
1179 
1180         return asciiDigit;
1181     }
1182 
1183     /** This function  shall be called to get the number of voicemails.
1184      * @hide
1185      */
1186     @UnsupportedAppUsage
1187     public int getNumOfVoicemails() {
1188         return mBearerData.numberOfMessages;
1189     }
1190 
1191     /**
1192      * Returns a byte array that can be use to uniquely identify a received SMS message.
1193      * C.S0015-B  4.3.1.6 Unique Message Identification.
1194      *
1195      * @return byte array uniquely identifying the message.
1196      * @hide
1197      */
1198     @UnsupportedAppUsage
1199     public byte[] getIncomingSmsFingerprint() {
1200         ByteArrayOutputStream output = new ByteArrayOutputStream();
1201 
1202         output.write(mEnvelope.serviceCategory);
1203         output.write(mEnvelope.teleService);
1204         output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
1205         output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
1206         // subaddress is not set when parsing some MT SMS.
1207         if (mEnvelope.origSubaddress != null && mEnvelope.origSubaddress.origBytes != null) {
1208             output.write(mEnvelope.origSubaddress.origBytes, 0,
1209                     mEnvelope.origSubaddress.origBytes.length);
1210         }
1211 
1212         return output.toByteArray();
1213     }
1214 
1215     /**
1216      * Returns the list of service category program data, if present.
1217      * @return a list of CdmaSmsCbProgramData objects, or null if not present
1218      * @hide
1219      */
1220     public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
1221         return mBearerData.serviceCategoryProgramData;
1222     }
1223 }
1224