1 /*
2  * Copyright (C) 2006 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.gsm;
18 
19 import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
20 import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
21 import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
22 import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
23 import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
24 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
25 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
26 import static com.android.internal.telephony.SmsConstants.MessageClass;
27 
28 import android.compat.annotation.UnsupportedAppUsage;
29 import android.content.res.Resources;
30 import android.os.Build;
31 import android.telephony.PhoneNumberUtils;
32 import android.text.TextUtils;
33 
34 import com.android.internal.telephony.EncodeException;
35 import com.android.internal.telephony.GsmAlphabet;
36 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
37 import com.android.internal.telephony.Sms7BitEncodingTranslator;
38 import com.android.internal.telephony.SmsHeader;
39 import com.android.internal.telephony.SmsMessageBase;
40 import com.android.internal.telephony.uicc.IccUtils;
41 import com.android.telephony.Rlog;
42 
43 import java.io.ByteArrayOutputStream;
44 import java.io.UnsupportedEncodingException;
45 import java.text.ParseException;
46 import java.time.DateTimeException;
47 import java.time.Instant;
48 import java.time.LocalDateTime;
49 import java.time.ZoneId;
50 import java.time.ZoneOffset;
51 import java.time.ZonedDateTime;
52 
53 /**
54  * A Short Message Service message.
55  *
56  */
57 public class SmsMessage extends SmsMessageBase {
58     static final String LOG_TAG = "SmsMessage";
59     private static final boolean VDBG = false;
60 
61     private MessageClass messageClass;
62 
63     /**
64      * TP-Message-Type-Indicator
65      * 9.2.3
66      */
67     private int mMti;
68 
69     /** TP-Protocol-Identifier (TP-PID) */
70     private int mProtocolIdentifier;
71 
72     // TP-Data-Coding-Scheme
73     // see TS 23.038
74     private int mDataCodingScheme;
75 
76     // TP-Reply-Path
77     // e.g. 23.040 9.2.2.1
78     private boolean mReplyPathPresent = false;
79 
80     /**
81      *  TP-Status - status of a previously submitted SMS.
82      *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
83      *  see TS 23.040, 9.2.3.15 for description of other possible values.
84      */
85     private int mStatus;
86 
87     /**
88      *  TP-Status - status of a previously submitted SMS.
89      *  This field is true iff the message is a SMS-STATUS-REPORT message.
90      */
91     private boolean mIsStatusReportMessage = false;
92 
93     private int mVoiceMailCount = 0;
94 
95     /** TP-Validity-Period-Format (TP-VPF). See TS 23.040, 9.2.3.3 */
96     private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
97     private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
98     private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
99     private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
100 
101     // Validity Period min - 5 mins
102     private static final int VALIDITY_PERIOD_MIN = 5;
103     // Validity Period max - 63 weeks
104     private static final int VALIDITY_PERIOD_MAX = 635040;
105 
106     private static final int INVALID_VALIDITY_PERIOD = -1;
107 
108     @UnsupportedAppUsage
SmsMessage()109     public SmsMessage() {
110     }
111 
112     public static class SubmitPdu extends SubmitPduBase {
113         @UnsupportedAppUsage
SubmitPdu()114         public SubmitPdu() {
115         }
116     }
117 
118     /**
119      * Create an SmsMessage from a raw PDU.
120      */
121     @UnsupportedAppUsage
createFromPdu(byte[] pdu)122     public static SmsMessage createFromPdu(byte[] pdu) {
123         try {
124             SmsMessage msg = new SmsMessage();
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             Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
132             return null;
133         }
134     }
135 
136     /**
137      * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
138      * by TP_PID field set to value 0x40
139      */
isTypeZero()140     public boolean isTypeZero() {
141         return (mProtocolIdentifier == 0x40);
142     }
143 
144     /**
145      * Creates an SmsMessage from an SMS EF record.
146      *
147      * @param index Index of SMS EF record.
148      * @param data Record data.
149      * @return An SmsMessage representing the record.
150      *
151      * @hide
152      */
153     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
154             + "android.telephony.SmsMessage} API instead")
createFromEfRecord(int index, byte[] data)155     public static SmsMessage createFromEfRecord(int index, byte[] data) {
156         try {
157             SmsMessage msg = new SmsMessage();
158 
159             msg.mIndexOnIcc = index;
160 
161             // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
162             // or STORED_UNSENT
163             // See TS 51.011 10.5.3
164             if ((data[0] & 1) == 0) {
165                 Rlog.w(LOG_TAG,
166                         "SMS parsing failed: Trying to parse a free record");
167                 return null;
168             } else {
169                 msg.mStatusOnIcc = data[0] & 0x07;
170             }
171 
172             int size = data.length - 1;
173 
174             // Note: Data may include trailing FF's.  That's OK; message
175             // should still parse correctly.
176             byte[] pdu = new byte[size];
177             System.arraycopy(data, 1, pdu, 0, size);
178             msg.parsePdu(pdu);
179             return msg;
180         } catch (RuntimeException ex) {
181             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
182             return null;
183         }
184     }
185 
186     /**
187      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
188      * length in bytes (not hex chars) less the SMSC header
189      */
getTPLayerLengthForPDU(String pdu)190     public static int getTPLayerLengthForPDU(String pdu) {
191         int len = pdu.length() / 2;
192         int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
193 
194         return len - smscLen - 1;
195     }
196 
197     /**
198      * Gets Encoded Relative Validity Period Value from Validity period in mins.
199      *
200      * @param validityPeriod Validity period in mins.
201      *
202      * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
203      * ------------------------------------------------------------
204      *        TP-VP       |            Validity period
205      *  (Relative format) |                 value
206      * ------------------------------------------------------------
207      *  0 to 143          | (TP-VP + 1) x 5 minutes
208      *  144 to 167        | 12 hours + ((TP-VP -143) x 30 minutes)
209      *  168 to 196        | (TP-VP - 166) x 1 day
210      *  197 to 255        | (TP-VP - 192) x 1 week
211      * ------------------------------------------------------------
212      *
213      * @return relValidityPeriod Encoded Relative Validity Period Value.
214      * @hide
215      */
getRelativeValidityPeriod(int validityPeriod)216     public static int getRelativeValidityPeriod(int validityPeriod) {
217         int relValidityPeriod = INVALID_VALIDITY_PERIOD;
218 
219         if (validityPeriod >= VALIDITY_PERIOD_MIN) {
220             if (validityPeriod <= 720) {
221                 relValidityPeriod = (validityPeriod / 5) - 1;
222             } else if (validityPeriod <= 1440) {
223                 relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
224             } else if (validityPeriod <= 43200) {
225                 relValidityPeriod = (validityPeriod / 1440) + 166;
226             } else if (validityPeriod <= VALIDITY_PERIOD_MAX) {
227                 relValidityPeriod = (validityPeriod / 10080) + 192;
228             }
229         }
230         return relValidityPeriod;
231     }
232 
233     /**
234      * Gets an SMS-SUBMIT PDU for a destination address and a message.
235      *
236      * @param scAddress Service Centre address. Null means use default.
237      * @param destinationAddress the address of the destination for the message.
238      * @param message string representation of the message payload.
239      * @param statusReportRequested indicates whether a report is reuested for this message.
240      * @param header a byte array containing the data for the User Data Header.
241      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
242      *         encoded message. Returns null on encode error.
243      * @hide
244      */
245     @UnsupportedAppUsage
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header)246     public static SubmitPdu getSubmitPdu(String scAddress,
247             String destinationAddress, String message,
248             boolean statusReportRequested, byte[] header) {
249         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
250                 ENCODING_UNKNOWN, 0, 0);
251     }
252 
253 
254     /**
255      * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding.
256      *
257      * @param scAddress Service Centre address. Null means use default.
258      * @param destinationAddress the address of the destination for the message.
259      * @param message string representation of the message payload.
260      * @param statusReportRequested indicates whether a report is reuested for this message.
261      * @param header a byte array containing the data for the User Data Header.
262      * @param encoding encoding defined by constants in
263      *                 com.android.internal.telephony.SmsConstants.ENCODING_*
264      * @param languageTable
265      * @param languageShiftTable
266      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
267      *         encoded message. Returns null on encode error.
268      * @hide
269      */
270     @UnsupportedAppUsage
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header, int encoding, int languageTable, int languageShiftTable)271     public static SubmitPdu getSubmitPdu(String scAddress,
272             String destinationAddress, String message,
273             boolean statusReportRequested, byte[] header, int encoding,
274             int languageTable, int languageShiftTable) {
275         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
276             header, encoding, languageTable, languageShiftTable, -1);
277     }
278 
279     /**
280      * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding.
281      *
282      * @param scAddress Service Centre address. Null means use default.
283      * @param destinationAddress the address of the destination for the message.
284      * @param message string representation of the message payload.
285      * @param statusReportRequested indicates whether a report is reuested for this message.
286      * @param header a byte array containing the data for the User Data Header.
287      * @param encoding encoding defined by constants in
288      *                 com.android.internal.telephony.SmsConstants.ENCODING_*
289      * @param languageTable
290      * @param languageShiftTable
291      * @param validityPeriod Validity Period of the message in Minutes.
292      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
293      *         encoded message. Returns null on encode error.
294      * @hide
295      */
296     @UnsupportedAppUsage
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header, int encoding, int languageTable, int languageShiftTable, int validityPeriod)297     public static SubmitPdu getSubmitPdu(String scAddress,
298             String destinationAddress, String message,
299             boolean statusReportRequested, byte[] header, int encoding,
300             int languageTable, int languageShiftTable, int validityPeriod) {
301 
302         // Perform null parameter checks.
303         if (message == null || destinationAddress == null) {
304             return null;
305         }
306 
307         if (encoding == ENCODING_UNKNOWN) {
308             // Find the best encoding to use
309             TextEncodingDetails ted = calculateLength(message, false);
310             encoding = ted.codeUnitSize;
311             languageTable = ted.languageTable;
312             languageShiftTable = ted.languageShiftTable;
313 
314             if (encoding == ENCODING_7BIT &&
315                     (languageTable != 0 || languageShiftTable != 0)) {
316                 if (header != null) {
317                     SmsHeader smsHeader = SmsHeader.fromByteArray(header);
318                     if (smsHeader.languageTable != languageTable
319                             || smsHeader.languageShiftTable != languageShiftTable) {
320                         Rlog.w(LOG_TAG, "Updating language table in SMS header: "
321                                 + smsHeader.languageTable + " -> " + languageTable + ", "
322                                 + smsHeader.languageShiftTable + " -> " + languageShiftTable);
323                         smsHeader.languageTable = languageTable;
324                         smsHeader.languageShiftTable = languageShiftTable;
325                         header = SmsHeader.toByteArray(smsHeader);
326                     }
327                 } else {
328                     SmsHeader smsHeader = new SmsHeader();
329                     smsHeader.languageTable = languageTable;
330                     smsHeader.languageShiftTable = languageShiftTable;
331                     header = SmsHeader.toByteArray(smsHeader);
332                 }
333             }
334         }
335 
336         SubmitPdu ret = new SubmitPdu();
337 
338         int relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod);
339 
340         byte mtiByte = 0x01; // SMS-SUBMIT
341 
342         if (header != null) {
343             // Set TP-UDHI
344             mtiByte |= 0x40;
345         }
346 
347         if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) {
348             // Set TP-Validity-Period-Format (TP-VPF)
349             mtiByte |= VALIDITY_PERIOD_FORMAT_RELATIVE << 3;
350         }
351 
352         ByteArrayOutputStream bo = getSubmitPduHead(
353                 scAddress, destinationAddress, mtiByte,
354                 statusReportRequested, ret);
355 
356         // Skip encoding pdu if error occurs when create pdu head and the error will be handled
357         // properly later on encodedMessage correctness check.
358         if (bo == null) return ret;
359 
360         // User Data (and length)
361         byte[] userData;
362         try {
363             if (encoding == ENCODING_7BIT) {
364                 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
365                         languageTable, languageShiftTable);
366             } else { //assume UCS-2
367                 try {
368                     userData = encodeUCS2(message, header);
369                 } catch(UnsupportedEncodingException uex) {
370                     Rlog.e(LOG_TAG,
371                             "Implausible UnsupportedEncodingException ",
372                             uex);
373                     return null;
374                 }
375             }
376         } catch (EncodeException ex) {
377             if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) {
378                 Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex);
379                 return null;
380             } else {
381                 // Encoding to the 7-bit alphabet failed. Let's see if we can
382                 // send it as a UCS-2 encoded message
383                 try {
384                     userData = encodeUCS2(message, header);
385                     encoding = ENCODING_16BIT;
386                 } catch (EncodeException ex1) {
387                     Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1);
388                     return null;
389                 } catch (UnsupportedEncodingException uex) {
390                     Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
391                     return null;
392                 }
393             }
394         }
395 
396         if (encoding == ENCODING_7BIT) {
397             if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
398                 // Message too long
399                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
400                 return null;
401             }
402             // TP-Data-Coding-Scheme
403             // Default encoding, uncompressed
404             // To test writing messages to the SIM card, change this value 0x00
405             // to 0x12, which means "bits 1 and 0 contain message class, and the
406             // class is 2". Note that this takes effect for the sender. In other
407             // words, messages sent by the phone with this change will end up on
408             // the receiver's SIM card. You can then send messages to yourself
409             // (on a phone with this change) and they'll end up on the SIM card.
410             bo.write(0x00);
411         } else { // assume UCS-2
412             if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
413                 // Message too long
414                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
415                 return null;
416             }
417             // TP-Data-Coding-Scheme
418             // UCS-2 encoding, uncompressed
419             bo.write(0x08);
420         }
421 
422         // TP-Validity-Period (TP-VP)
423         if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) {
424             bo.write(relativeValidityPeriod);
425         }
426 
427         bo.write(userData, 0, userData.length);
428         ret.encodedMessage = bo.toByteArray();
429         return ret;
430     }
431 
432     /**
433      * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
434      *
435      * @return encoded message as UCS2
436      * @throws UnsupportedEncodingException
437      * @throws EncodeException if String is too large to encode
438      */
439     @UnsupportedAppUsage
encodeUCS2(String message, byte[] header)440     private static byte[] encodeUCS2(String message, byte[] header)
441             throws UnsupportedEncodingException, EncodeException {
442         byte[] userData, textPart;
443         textPart = message.getBytes("utf-16be");
444 
445         if (header != null) {
446             // Need 1 byte for UDHL
447             userData = new byte[header.length + textPart.length + 1];
448 
449             userData[0] = (byte)header.length;
450             System.arraycopy(header, 0, userData, 1, header.length);
451             System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
452         }
453         else {
454             userData = textPart;
455         }
456         if (userData.length > 255) {
457             throw new EncodeException(
458                     "Payload cannot exceed 255 bytes", EncodeException.ERROR_EXCEED_SIZE);
459         }
460         byte[] ret = new byte[userData.length+1];
461         ret[0] = (byte) (userData.length & 0xff );
462         System.arraycopy(userData, 0, ret, 1, userData.length);
463         return ret;
464     }
465 
466     /**
467      * Gets an SMS-SUBMIT PDU for a destination address and a message.
468      *
469      * @param scAddress Service Centre address. Null means use default.
470      * @param destinationAddress the address of the destination for the message.
471      * @param message string representation of the message payload.
472      * @param statusReportRequested indicates whether a report is reuested for this message.
473      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
474      *         encoded message. Returns null on encode error.
475      */
476     @UnsupportedAppUsage
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested)477     public static SubmitPdu getSubmitPdu(String scAddress,
478             String destinationAddress, String message,
479             boolean statusReportRequested) {
480 
481         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
482     }
483 
484     /**
485      * Gets an SMS-SUBMIT PDU for a destination address and a message.
486      *
487      * @param scAddress Service Centre address. Null means use default.
488      * @param destinationAddress the address of the destination for the message.
489      * @param message string representation of the message payload.
490      * @param statusReportRequested indicates whether a report is reuested for this message.
491      * @param validityPeriod Validity Period of the message in Minutes.
492      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
493      *         encoded message. Returns null on encode error.
494      */
495     @UnsupportedAppUsage
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, int validityPeriod)496     public static SubmitPdu getSubmitPdu(String scAddress,
497             String destinationAddress, String message,
498             boolean statusReportRequested, int validityPeriod) {
499         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
500                 null, ENCODING_UNKNOWN, 0, 0, validityPeriod);
501     }
502 
503     /**
504      * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
505      *
506      * @param scAddress Service Centre address. Null means use default.
507      * @param destinationAddress the address of the destination for the message.
508      * @param destinationPort the port to deliver the message to at the destination.
509      * @param data the data for the message.
510      * @param statusReportRequested indicates whether a report is reuested for this message.
511      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
512      *         encoded message. Returns null on encode error.
513      */
getSubmitPdu(String scAddress, String destinationAddress, int destinationPort, byte[] data, boolean statusReportRequested)514     public static SubmitPdu getSubmitPdu(String scAddress,
515             String destinationAddress, int destinationPort, byte[] data,
516             boolean statusReportRequested) {
517 
518         SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
519         portAddrs.destPort = destinationPort;
520         portAddrs.origPort = 0;
521         portAddrs.areEightBits = false;
522 
523         SmsHeader smsHeader = new SmsHeader();
524         smsHeader.portAddrs = portAddrs;
525 
526         byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
527 
528         if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
529             Rlog.e(LOG_TAG, "SMS data message may only contain "
530                     + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
531             return null;
532         }
533 
534         SubmitPdu ret = new SubmitPdu();
535         ByteArrayOutputStream bo = getSubmitPduHead(
536                 scAddress, destinationAddress, (byte) 0x41, /* TP-MTI=SMS-SUBMIT, TP-UDHI=true */
537                 statusReportRequested, ret);
538         // Skip encoding pdu if error occurs when create pdu head and the error will be handled
539         // properly later on encodedMessage correctness check.
540         if (bo == null) return ret;
541 
542         // TP-Data-Coding-Scheme
543         // No class, 8 bit data
544         bo.write(0x04);
545 
546         // (no TP-Validity-Period)
547 
548         // Total size
549         bo.write(data.length + smsHeaderData.length + 1);
550 
551         // User data header
552         bo.write(smsHeaderData.length);
553         bo.write(smsHeaderData, 0, smsHeaderData.length);
554 
555         // User data
556         bo.write(data, 0, data.length);
557 
558         ret.encodedMessage = bo.toByteArray();
559         return ret;
560     }
561 
562     /**
563      * Creates the beginning of a SUBMIT PDU.
564      *
565      * This is the part of the SUBMIT PDU that is common to the two versions of
566      * {@link #getSubmitPdu}, one of which takes a byte array and the other of which takes a
567      * <code>String</code>.
568      *
569      * @param scAddress Service Centre address. Null means use default.
570      * @param destinationAddress the address of the destination for the message.
571      * @param mtiByte
572      * @param statusReportRequested indicates whether a report is reuested for this message.
573      * @param ret <code>SubmitPdu</code>.
574      * @return a byte array of the beginning of a SUBMIT PDU. Null for invalid destinationAddress.
575      */
576     @UnsupportedAppUsage
getSubmitPduHead( String scAddress, String destinationAddress, byte mtiByte, boolean statusReportRequested, SubmitPdu ret)577     private static ByteArrayOutputStream getSubmitPduHead(
578             String scAddress, String destinationAddress, byte mtiByte,
579             boolean statusReportRequested, SubmitPdu ret) {
580         ByteArrayOutputStream bo = new ByteArrayOutputStream(
581                 MAX_USER_DATA_BYTES + 40);
582 
583         // SMSC address with length octet, or 0
584         if (scAddress == null) {
585             ret.encodedScAddress = null;
586         } else {
587             ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
588                     scAddress);
589         }
590 
591         // TP-Message-Type-Indicator (and friends)
592         if (statusReportRequested) {
593             // Set TP-Status-Report-Request bit.
594             mtiByte |= 0x20;
595             if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
596         }
597         bo.write(mtiByte);
598 
599         // space for TP-Message-Reference
600         bo.write(0);
601 
602         byte[] daBytes;
603 
604         daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
605 
606         // return empty pduHead for invalid destination address
607         if (daBytes == null) return null;
608 
609         // destination address length in BCD digits, ignoring TON byte and pad
610         // TODO Should be better.
611         bo.write((daBytes.length - 1) * 2
612                 - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
613 
614         // destination address
615         bo.write(daBytes, 0, daBytes.length);
616 
617         // TP-Protocol-Identifier
618         bo.write(0);
619         return bo;
620     }
621 
622     /**
623      * Gets an SMS-DELIVER PDU for an originating address and a message.
624      *
625      * @param scAddress Service Centre address. Null means use default.
626      * @param originatingAddress the address of the originating for the message.
627      * @param message string representation of the message payload.
628      * @param date the time stamp the message was received.
629      * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
630      *         encoded message. Returns null on encode error.
631      * @hide
632      */
getDeliverPdu( String scAddress, String originatingAddress, String message, long date)633     public static SubmitPdu getDeliverPdu(
634             String scAddress, String originatingAddress, String message, long date) {
635         if (originatingAddress == null || message == null) {
636             return null;
637         }
638 
639         // Find the best encoding to use
640         TextEncodingDetails ted = calculateLength(message, false);
641         int encoding = ted.codeUnitSize;
642         int languageTable = ted.languageTable;
643         int languageShiftTable = ted.languageShiftTable;
644         byte[] header = null;
645 
646         if (encoding == ENCODING_7BIT && (languageTable != 0 || languageShiftTable != 0)) {
647             SmsHeader smsHeader = new SmsHeader();
648             smsHeader.languageTable = languageTable;
649             smsHeader.languageShiftTable = languageShiftTable;
650             header = SmsHeader.toByteArray(smsHeader);
651         }
652 
653         SubmitPdu ret = new SubmitPdu();
654 
655         ByteArrayOutputStream bo = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40);
656 
657         // SMSC address with length octet, or 0
658         if (scAddress == null) {
659             ret.encodedScAddress = null;
660         } else {
661             ret.encodedScAddress =
662                     PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(scAddress);
663         }
664 
665         // TP-Message-Type-Indicator
666         bo.write(0); // SMS-DELIVER
667 
668         byte[] oaBytes;
669 
670         oaBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(originatingAddress);
671 
672         // Return null for invalid originating address
673         if (oaBytes == null) return null;
674 
675         // Originating address length in BCD digits, ignoring TON byte and pad
676         // TODO Should be better.
677         bo.write((oaBytes.length - 1) * 2 - ((oaBytes[oaBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
678 
679         // Originating Address
680         bo.write(oaBytes, 0, oaBytes.length);
681 
682         // TP-Protocol-Identifier
683         bo.write(0);
684 
685         // User Data (and length)
686         byte[] userData;
687         try {
688             if (encoding == ENCODING_7BIT) {
689                 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
690                         languageTable, languageShiftTable);
691             } else { // Assume UCS-2
692                 try {
693                     userData = encodeUCS2(message, header);
694                 } catch (UnsupportedEncodingException uex) {
695                     Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
696                     return null;
697                 }
698             }
699         } catch (EncodeException ex) {
700             if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) {
701                 Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex);
702                 return null;
703             } else {
704                 // Encoding to the 7-bit alphabet failed. Let's see if we can send it as a UCS-2
705                 // encoded message
706                 try {
707                     userData = encodeUCS2(message, header);
708                     encoding = ENCODING_16BIT;
709                 } catch (EncodeException ex1) {
710                     Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1);
711                     return null;
712                 } catch (UnsupportedEncodingException uex) {
713                     Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
714                     return null;
715                 }
716             }
717         }
718 
719         if (encoding == ENCODING_7BIT) {
720             if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
721                 // Message too long
722                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
723                 return null;
724             }
725             // TP-Data-Coding-Scheme
726             // Default encoding, uncompressed
727             bo.write(0x00);
728         } else { // Assume UCS-2
729             if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
730                 // Message too long
731                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
732                 return null;
733             }
734             // TP-Data-Coding-Scheme
735             // UCS-2 encoding, uncompressed
736             bo.write(0x08);
737         }
738 
739         // TP-Service-Centre-Time-Stamp
740         byte[] scts = new byte[7];
741 
742         ZonedDateTime zoneDateTime = Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault());
743         LocalDateTime localDateTime = zoneDateTime.toLocalDateTime();
744 
745         // It indicates the difference, expressed in quarters of an hour, between the local time and
746         // GMT.
747         int timezoneOffset = zoneDateTime.getOffset().getTotalSeconds() / 60 / 15;
748         boolean negativeOffset = timezoneOffset < 0;
749         if (negativeOffset) {
750             timezoneOffset = -timezoneOffset;
751         }
752         int year = localDateTime.getYear();
753         int month = localDateTime.getMonthValue();
754         int day = localDateTime.getDayOfMonth();
755         int hour = localDateTime.getHour();
756         int minute = localDateTime.getMinute();
757         int second = localDateTime.getSecond();
758 
759         year = year > 2000 ? year - 2000 : year - 1900;
760         scts[0] = (byte) ((((year % 10) & 0x0F) << 4) | ((year / 10) & 0x0F));
761         scts[1] = (byte) ((((month % 10) & 0x0F) << 4) | ((month / 10) & 0x0F));
762         scts[2] = (byte) ((((day % 10) & 0x0F) << 4) | ((day / 10) & 0x0F));
763         scts[3] = (byte) ((((hour % 10) & 0x0F) << 4) | ((hour / 10) & 0x0F));
764         scts[4] = (byte) ((((minute % 10) & 0x0F) << 4) | ((minute / 10) & 0x0F));
765         scts[5] = (byte) ((((second % 10) & 0x0F) << 4) | ((second / 10) & 0x0F));
766         scts[6] = (byte) ((((timezoneOffset % 10) & 0x0F) << 4) | ((timezoneOffset / 10) & 0x0F));
767         if (negativeOffset) {
768             scts[0] |= 0x08; // Negative offset
769         }
770         bo.write(scts, 0, scts.length);
771 
772         bo.write(userData, 0, userData.length);
773         ret.encodedMessage = bo.toByteArray();
774         return ret;
775     }
776 
777     private static class PduParser {
778         @UnsupportedAppUsage
779         byte mPdu[];
780         @UnsupportedAppUsage
781         int mCur;
782         SmsHeader mUserDataHeader;
783         byte[] mUserData;
784         @UnsupportedAppUsage
785         int mUserDataSeptetPadding;
786 
787         @UnsupportedAppUsage
PduParser(byte[] pdu)788         PduParser(byte[] pdu) {
789             mPdu = pdu;
790             mCur = 0;
791             mUserDataSeptetPadding = 0;
792         }
793 
794         /**
795          * Parse and return the SC address prepended to SMS messages coming via
796          * the TS 27.005 / AT interface.  Returns null on invalid address
797          */
getSCAddress()798         String getSCAddress() {
799             int len;
800             String ret;
801 
802             // length of SC Address
803             len = getByte();
804 
805             if (len == 0) {
806                 // no SC address
807                 ret = null;
808             } else {
809                 // SC address
810                 try {
811                     ret = PhoneNumberUtils.calledPartyBCDToString(
812                             mPdu, mCur, len, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
813                 } catch (RuntimeException tr) {
814                     Rlog.d(LOG_TAG, "invalid SC address: ", tr);
815                     ret = null;
816                 }
817             }
818 
819             mCur += len;
820 
821             return ret;
822         }
823 
824         /**
825          * returns non-sign-extended byte value
826          */
827         @UnsupportedAppUsage
getByte()828         int getByte() {
829             return mPdu[mCur++] & 0xff;
830         }
831 
832         /**
833          * Any address except the SC address (eg, originating address) See TS
834          * 23.040 9.1.2.5
835          */
getAddress()836         GsmSmsAddress getAddress() {
837             GsmSmsAddress ret;
838 
839             // "The Address-Length field is an integer representation of
840             // the number field, i.e. excludes any semi-octet containing only
841             // fill bits."
842             // The TOA field is not included as part of this
843             int addressLength = mPdu[mCur] & 0xff;
844             int lengthBytes = 2 + (addressLength + 1) / 2;
845 
846             try {
847                 ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
848             } catch (ParseException e) {
849                 ret = null;
850                 //This is caught by createFromPdu(byte[] pdu)
851                 throw new RuntimeException(e.getMessage());
852             }
853 
854             mCur += lengthBytes;
855 
856             return ret;
857         }
858 
859         /**
860          * Parses an SC timestamp and returns a currentTimeMillis()-style timestamp, or 0 if
861          * invalid.
862          */
getSCTimestampMillis()863         long getSCTimestampMillis() {
864             // TP-Service-Centre-Time-Stamp
865             int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
866             int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
867             int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
868             int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
869             int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
870             int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
871 
872             // For the timezone, the most significant bit of the
873             // least significant nibble is the sign byte
874             // (meaning the max range of this field is 79 quarter-hours,
875             // which is more than enough)
876 
877             byte tzByte = mPdu[mCur++];
878 
879             // Mask out sign bit.
880             int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
881 
882             timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
883             // timezoneOffset is in quarter hours.
884             int timeZoneOffsetSeconds = timezoneOffset * 15 * 60;
885 
886             // It's 2006.  Should I really support years < 2000?
887             int fullYear = year >= 90 ? year + 1900 : year + 2000;
888             try {
889                 LocalDateTime localDateTime = LocalDateTime.of(
890                         fullYear,
891                         month /* 1-12 */,
892                         day,
893                         hour,
894                         minute,
895                         second);
896                 long epochSeconds =
897                         localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
898                 // Convert to milliseconds.
899                 return epochSeconds * 1000;
900             } catch (DateTimeException ex) {
901                 Rlog.e(LOG_TAG, "Invalid timestamp", ex);
902             }
903             return 0;
904         }
905 
906         /**
907          * Pulls the user data out of the PDU, and separates the payload from
908          * the header if there is one.
909          *
910          * @param hasUserDataHeader true if there is a user data header
911          * @param dataInSeptets true if the data payload is in septets instead
912          *  of octets
913          * @return the number of septets or octets in the user data payload
914          */
constructUserData(boolean hasUserDataHeader, boolean dataInSeptets)915         int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
916             int offset = mCur;
917             int userDataLength = mPdu[offset++] & 0xff;
918             int headerSeptets = 0;
919             int userDataHeaderLength = 0;
920 
921             if (hasUserDataHeader) {
922                 userDataHeaderLength = mPdu[offset++] & 0xff;
923 
924                 byte[] udh = new byte[userDataHeaderLength];
925                 System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
926                 mUserDataHeader = SmsHeader.fromByteArray(udh);
927                 offset += userDataHeaderLength;
928 
929                 int headerBits = (userDataHeaderLength + 1) * 8;
930                 headerSeptets = headerBits / 7;
931                 headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
932                 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
933             }
934 
935             int bufferLen;
936             if (dataInSeptets) {
937                 /*
938                  * Here we just create the user data length to be the remainder of
939                  * the pdu minus the user data header, since userDataLength means
940                  * the number of uncompressed septets.
941                  */
942                 bufferLen = mPdu.length - offset;
943             } else {
944                 /*
945                  * userDataLength is the count of octets, so just subtract the
946                  * user data header.
947                  */
948                 bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
949                 if (bufferLen < 0) {
950                     bufferLen = 0;
951                 }
952             }
953 
954             mUserData = new byte[bufferLen];
955             System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
956             mCur = offset;
957 
958             if (dataInSeptets) {
959                 // Return the number of septets
960                 int count = userDataLength - headerSeptets;
961                 // If count < 0, return 0 (means UDL was probably incorrect)
962                 return count < 0 ? 0 : count;
963             } else {
964                 // Return the number of octets
965                 return mUserData.length;
966             }
967         }
968 
969         /**
970          * Returns the user data payload, not including the headers
971          *
972          * @return the user data payload, not including the headers
973          */
974         @UnsupportedAppUsage
getUserData()975         byte[] getUserData() {
976             return mUserData;
977         }
978 
979         /**
980          * Returns an object representing the user data headers
981          *
982          * {@hide}
983          */
getUserDataHeader()984         SmsHeader getUserDataHeader() {
985             return mUserDataHeader;
986         }
987 
988         /**
989          * Interprets the user data payload as packed GSM 7bit characters, and
990          * decodes them into a String.
991          *
992          * @param septetCount the number of septets in the user data payload
993          * @return a String with the decoded characters
994          */
getUserDataGSM7Bit(int septetCount, int languageTable, int languageShiftTable)995         String getUserDataGSM7Bit(int septetCount, int languageTable,
996                 int languageShiftTable) {
997             String ret;
998 
999             ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
1000                     mUserDataSeptetPadding, languageTable, languageShiftTable);
1001 
1002             mCur += (septetCount * 7) / 8;
1003 
1004             return ret;
1005         }
1006 
1007         /**
1008          * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's
1009          * stored in 8-bit unpacked format) characters, and decodes them into a String.
1010          *
1011          * @param byteCount the number of byest in the user data payload
1012          * @return a String with the decoded characters
1013          */
getUserDataGSM8bit(int byteCount)1014         String getUserDataGSM8bit(int byteCount) {
1015             String ret;
1016 
1017             ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount);
1018 
1019             mCur += byteCount;
1020 
1021             return ret;
1022         }
1023 
1024         /**
1025          * Interprets the user data payload as UCS2 characters, and
1026          * decodes them into a String.
1027          *
1028          * @param byteCount the number of bytes in the user data payload
1029          * @return a String with the decoded characters
1030          */
1031         @UnsupportedAppUsage
getUserDataUCS2(int byteCount)1032         String getUserDataUCS2(int byteCount) {
1033             String ret;
1034 
1035             try {
1036                 ret = new String(mPdu, mCur, byteCount, "utf-16");
1037             } catch (UnsupportedEncodingException ex) {
1038                 ret = "";
1039                 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
1040             }
1041 
1042             mCur += byteCount;
1043             return ret;
1044         }
1045 
1046         /**
1047          * Interprets the user data payload as KSC-5601 characters, and
1048          * decodes them into a String.
1049          *
1050          * @param byteCount the number of bytes in the user data payload
1051          * @return a String with the decoded characters
1052          */
getUserDataKSC5601(int byteCount)1053         String getUserDataKSC5601(int byteCount) {
1054             String ret;
1055 
1056             try {
1057                 ret = new String(mPdu, mCur, byteCount, "KSC5601");
1058             } catch (UnsupportedEncodingException ex) {
1059                 ret = "";
1060                 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
1061             }
1062 
1063             mCur += byteCount;
1064             return ret;
1065         }
1066 
moreDataPresent()1067         boolean moreDataPresent() {
1068             return (mPdu.length > mCur);
1069         }
1070     }
1071 
1072     /**
1073      * Calculates the number of SMS's required to encode the message body and
1074      * the number of characters remaining until the next message.
1075      *
1076      * @param msgBody the message to encode
1077      * @param use7bitOnly ignore (but still count) illegal characters if true
1078      * @return TextEncodingDetails
1079      */
1080     @UnsupportedAppUsage
calculateLength(CharSequence msgBody, boolean use7bitOnly)1081     public static TextEncodingDetails calculateLength(CharSequence msgBody,
1082             boolean use7bitOnly) {
1083         CharSequence newMsgBody = null;
1084         Resources r = Resources.getSystem();
1085         if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
1086             newMsgBody = Sms7BitEncodingTranslator.translate(msgBody, false /* isCdmaFormat */);
1087         }
1088         if (TextUtils.isEmpty(newMsgBody)) {
1089             newMsgBody = msgBody;
1090         }
1091         TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
1092         if (ted == null) {
1093             return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
1094         }
1095         return ted;
1096     }
1097 
1098     /** {@inheritDoc} */
1099     @Override
getProtocolIdentifier()1100     public int getProtocolIdentifier() {
1101         return mProtocolIdentifier;
1102     }
1103 
1104     /**
1105      * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
1106      * @return the TP-DCS field of the SMS header
1107      */
getDataCodingScheme()1108     int getDataCodingScheme() {
1109         return mDataCodingScheme;
1110     }
1111 
1112     /** {@inheritDoc} */
1113     @Override
isReplace()1114     public boolean isReplace() {
1115         return (mProtocolIdentifier & 0xc0) == 0x40
1116                 && (mProtocolIdentifier & 0x3f) > 0
1117                 && (mProtocolIdentifier & 0x3f) < 8;
1118     }
1119 
1120     /** {@inheritDoc} */
1121     @Override
isCphsMwiMessage()1122     public boolean isCphsMwiMessage() {
1123         return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
1124                 || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
1125     }
1126 
1127     /** {@inheritDoc} */
1128     @UnsupportedAppUsage
1129     @Override
isMWIClearMessage()1130     public boolean isMWIClearMessage() {
1131         if (mIsMwi && !mMwiSense) {
1132             return true;
1133         }
1134 
1135         return mOriginatingAddress != null
1136                 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
1137     }
1138 
1139     /** {@inheritDoc} */
1140     @UnsupportedAppUsage
1141     @Override
isMWISetMessage()1142     public boolean isMWISetMessage() {
1143         if (mIsMwi && mMwiSense) {
1144             return true;
1145         }
1146 
1147         return mOriginatingAddress != null
1148                 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
1149     }
1150 
1151     /** {@inheritDoc} */
1152     @UnsupportedAppUsage
1153     @Override
isMwiDontStore()1154     public boolean isMwiDontStore() {
1155         if (mIsMwi && mMwiDontStore) {
1156             return true;
1157         }
1158 
1159         if (isCphsMwiMessage()) {
1160             // See CPHS 4.2 Section B.4.2.1
1161             // If the user data is a single space char, do not store
1162             // the message. Otherwise, store and display as usual
1163             if (" ".equals(getMessageBody())) {
1164                 return true;
1165             }
1166         }
1167 
1168         return false;
1169     }
1170 
1171     /** {@inheritDoc} */
1172     @UnsupportedAppUsage
1173     @Override
getStatus()1174     public int getStatus() {
1175         return mStatus;
1176     }
1177 
1178     /** {@inheritDoc} */
1179     @UnsupportedAppUsage
1180     @Override
isStatusReportMessage()1181     public boolean isStatusReportMessage() {
1182         return mIsStatusReportMessage;
1183     }
1184 
1185     /** {@inheritDoc} */
1186     @Override
isReplyPathPresent()1187     public boolean isReplyPathPresent() {
1188         return mReplyPathPresent;
1189     }
1190 
1191     /**
1192      * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
1193      * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
1194      * ME/TA converts each octet of TP data unit into two IRA character long
1195      * hex number (e.g. octet with integer value 42 is presented to TE as two
1196      * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
1197      * something else...
1198      */
parsePdu(byte[] pdu)1199     private void parsePdu(byte[] pdu) {
1200         mPdu = pdu;
1201         // Rlog.d(LOG_TAG, "raw sms message:");
1202         // Rlog.d(LOG_TAG, s);
1203 
1204         PduParser p = new PduParser(pdu);
1205 
1206         mScAddress = p.getSCAddress();
1207 
1208         if (mScAddress != null) {
1209             if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
1210         }
1211 
1212         // TODO(mkf) support reply path, user data header indicator
1213 
1214         // TP-Message-Type-Indicator
1215         // 9.2.3
1216         int firstByte = p.getByte();
1217 
1218         mMti = firstByte & 0x3;
1219         switch (mMti) {
1220         // TP-Message-Type-Indicator
1221         // 9.2.3
1222         case 0:
1223         case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
1224                 //This should be processed in the same way as MTI == 0 (Deliver)
1225             parseSmsDeliver(p, firstByte);
1226             break;
1227         case 1:
1228             parseSmsSubmit(p, firstByte);
1229             break;
1230         case 2:
1231             parseSmsStatusReport(p, firstByte);
1232             break;
1233         default:
1234             // TODO(mkf) the rest of these
1235             throw new RuntimeException("Unsupported message type");
1236         }
1237     }
1238 
1239     /**
1240      * Parses a SMS-STATUS-REPORT message.
1241      *
1242      * @param p A PduParser, cued past the first byte.
1243      * @param firstByte The first byte of the PDU, which contains MTI, etc.
1244      */
parseSmsStatusReport(PduParser p, int firstByte)1245     private void parseSmsStatusReport(PduParser p, int firstByte) {
1246         mIsStatusReportMessage = true;
1247 
1248         // TP-Message-Reference
1249         mMessageRef = p.getByte();
1250         // TP-Recipient-Address
1251         mRecipientAddress = p.getAddress();
1252         // TP-Service-Centre-Time-Stamp
1253         mScTimeMillis = p.getSCTimestampMillis();
1254         // TP-Discharge-Time
1255         p.getSCTimestampMillis();
1256         // TP-Status
1257         mStatus = p.getByte();
1258 
1259         // The following are optional fields that may or may not be present.
1260         if (p.moreDataPresent()) {
1261             // TP-Parameter-Indicator
1262             int extraParams = p.getByte();
1263             int moreExtraParams = extraParams;
1264             while ((moreExtraParams & 0x80) != 0) {
1265                 // We only know how to parse a few extra parameters, all
1266                 // indicated in the first TP-PI octet, so skip over any
1267                 // additional TP-PI octets.
1268                 moreExtraParams = p.getByte();
1269             }
1270             // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
1271             // only process the byte if the reserved bits (bits3 to 6) are zero.
1272             if ((extraParams & 0x78) == 0) {
1273                 // TP-Protocol-Identifier
1274                 if ((extraParams & 0x01) != 0) {
1275                     mProtocolIdentifier = p.getByte();
1276                 }
1277                 // TP-Data-Coding-Scheme
1278                 if ((extraParams & 0x02) != 0) {
1279                     mDataCodingScheme = p.getByte();
1280                 }
1281                 // TP-User-Data-Length (implies existence of TP-User-Data)
1282                 if ((extraParams & 0x04) != 0) {
1283                     boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1284                     parseUserData(p, hasUserDataHeader);
1285                 }
1286             }
1287         }
1288     }
1289 
parseSmsDeliver(PduParser p, int firstByte)1290     private void parseSmsDeliver(PduParser p, int firstByte) {
1291         mReplyPathPresent = (firstByte & 0x80) == 0x80;
1292 
1293         mOriginatingAddress = p.getAddress();
1294 
1295         if (mOriginatingAddress != null) {
1296             if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
1297                     + mOriginatingAddress.address);
1298         }
1299 
1300         // TP-Protocol-Identifier (TP-PID)
1301         // TS 23.040 9.2.3.9
1302         mProtocolIdentifier = p.getByte();
1303 
1304         // TP-Data-Coding-Scheme
1305         // see TS 23.038
1306         mDataCodingScheme = p.getByte();
1307 
1308         if (VDBG) {
1309             Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
1310                     + " data coding scheme: " + mDataCodingScheme);
1311         }
1312 
1313         // TP-Service-Centre-Time-Stamp
1314         mScTimeMillis = p.getSCTimestampMillis();
1315 
1316         if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
1317 
1318         boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1319 
1320         parseUserData(p, hasUserDataHeader);
1321     }
1322 
1323     /**
1324      * Parses a SMS-SUBMIT message.
1325      *
1326      * @param p A PduParser, cued past the first byte.
1327      * @param firstByte The first byte of the PDU, which contains MTI, etc.
1328      */
parseSmsSubmit(PduParser p, int firstByte)1329     private void parseSmsSubmit(PduParser p, int firstByte) {
1330         mReplyPathPresent = (firstByte & 0x80) == 0x80;
1331 
1332         // TP-MR (TP-Message Reference)
1333         mMessageRef = p.getByte();
1334 
1335         mRecipientAddress = p.getAddress();
1336 
1337         if (mRecipientAddress != null) {
1338             if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
1339         }
1340 
1341         // TP-Protocol-Identifier (TP-PID)
1342         // TS 23.040 9.2.3.9
1343         mProtocolIdentifier = p.getByte();
1344 
1345         // TP-Data-Coding-Scheme
1346         // see TS 23.038
1347         mDataCodingScheme = p.getByte();
1348 
1349         if (VDBG) {
1350             Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
1351                     + " data coding scheme: " + mDataCodingScheme);
1352         }
1353 
1354         // TP-Validity-Period-Format
1355         int validityPeriodLength = 0;
1356         int validityPeriodFormat = ((firstByte >> 3) & 0x3);
1357         if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_NONE) {
1358             validityPeriodLength = 0;
1359         } else if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
1360             validityPeriodLength = 1;
1361         } else { // VALIDITY_PERIOD_FORMAT_ENHANCED or VALIDITY_PERIOD_FORMAT_ABSOLUTE
1362             validityPeriodLength = 7;
1363         }
1364 
1365         // TP-Validity-Period is not used on phone, so just ignore it for now.
1366         while (validityPeriodLength-- > 0) {
1367             p.getByte();
1368         }
1369 
1370         boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1371 
1372         parseUserData(p, hasUserDataHeader);
1373     }
1374 
1375     /**
1376      * Parses the User Data of an SMS.
1377      *
1378      * @param p The current PduParser.
1379      * @param hasUserDataHeader Indicates whether a header is present in the
1380      *                          User Data.
1381      */
parseUserData(PduParser p, boolean hasUserDataHeader)1382     private void parseUserData(PduParser p, boolean hasUserDataHeader) {
1383         boolean hasMessageClass = false;
1384         boolean userDataCompressed = false;
1385 
1386         int encodingType = ENCODING_UNKNOWN;
1387 
1388         Resources r = Resources.getSystem();
1389         // Look up the data encoding scheme
1390         if ((mDataCodingScheme & 0x80) == 0) {
1391             userDataCompressed = (0 != (mDataCodingScheme & 0x20));
1392             hasMessageClass = (0 != (mDataCodingScheme & 0x10));
1393 
1394             if (userDataCompressed) {
1395                 Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
1396                         + "(compression) " + (mDataCodingScheme & 0xff));
1397             } else {
1398                 switch ((mDataCodingScheme >> 2) & 0x3) {
1399                 case 0: // GSM 7 bit default alphabet
1400                     encodingType = ENCODING_7BIT;
1401                     break;
1402 
1403                 case 2: // UCS 2 (16bit)
1404                     encodingType = ENCODING_16BIT;
1405                     break;
1406 
1407                 case 1: // 8 bit data
1408                     //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
1409                     //that's stored in 8-bit unpacked format) characters.
1410                     if (r.getBoolean(com.android.internal.
1411                             R.bool.config_sms_decode_gsm_8bit_data)) {
1412                         encodingType = ENCODING_8BIT;
1413                         break;
1414                     }
1415 
1416                 case 3: // reserved
1417                     Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
1418                             + (mDataCodingScheme & 0xff));
1419                     encodingType = r.getInteger(
1420                             com.android.internal.R.integer.default_reserved_data_coding_scheme);
1421                     break;
1422                 }
1423             }
1424         } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
1425             hasMessageClass = true;
1426             userDataCompressed = false;
1427 
1428             if (0 == (mDataCodingScheme & 0x04)) {
1429                 // GSM 7 bit default alphabet
1430                 encodingType = ENCODING_7BIT;
1431             } else {
1432                 // 8 bit data
1433                 encodingType = ENCODING_8BIT;
1434             }
1435         } else if ((mDataCodingScheme & 0xF0) == 0xC0
1436                 || (mDataCodingScheme & 0xF0) == 0xD0
1437                 || (mDataCodingScheme & 0xF0) == 0xE0) {
1438             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1439 
1440             // 0xC0 == 7 bit, don't store
1441             // 0xD0 == 7 bit, store
1442             // 0xE0 == UCS-2, store
1443 
1444             if ((mDataCodingScheme & 0xF0) == 0xE0) {
1445                 encodingType = ENCODING_16BIT;
1446             } else {
1447                 encodingType = ENCODING_7BIT;
1448             }
1449 
1450             userDataCompressed = false;
1451             boolean active = ((mDataCodingScheme & 0x08) == 0x08);
1452             // bit 0x04 reserved
1453 
1454             // VM - If TP-UDH is present, these values will be overwritten
1455             if ((mDataCodingScheme & 0x03) == 0x00) {
1456                 mIsMwi = true; /* Indicates vmail */
1457                 mMwiSense = active;/* Indicates vmail notification set/clear */
1458                 mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
1459 
1460                 /* Set voice mail count based on notification bit */
1461                 if (active == true) {
1462                     mVoiceMailCount = -1; // unknown number of messages waiting
1463                 } else {
1464                     mVoiceMailCount = 0; // no unread messages
1465                 }
1466 
1467                 Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = "
1468                         + (mDataCodingScheme & 0xff) + " Dont store = "
1469                         + mMwiDontStore + " vmail count = " + mVoiceMailCount);
1470 
1471             } else {
1472                 mIsMwi = false;
1473                 Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: "
1474                         + (mDataCodingScheme & 0xff));
1475             }
1476         } else if ((mDataCodingScheme & 0xC0) == 0x80) {
1477             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1478             // 0x80..0xBF == Reserved coding groups
1479             if (mDataCodingScheme == 0x84) {
1480                 // This value used for KSC5601 by carriers in Korea.
1481                 encodingType = ENCODING_KSC5601;
1482             } else {
1483                 Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
1484                         + (mDataCodingScheme & 0xff));
1485             }
1486         } else {
1487             Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
1488                     + (mDataCodingScheme & 0xff));
1489         }
1490 
1491         // set both the user data and the user data header.
1492         int count = p.constructUserData(hasUserDataHeader,
1493                 encodingType == ENCODING_7BIT);
1494         this.mUserData = p.getUserData();
1495         this.mUserDataHeader = p.getUserDataHeader();
1496 
1497         /*
1498          * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
1499          * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND)
1500          * ieidl =2 octets
1501          * ieda msg_ind_type = 0x00 (voice mail; discard sms )or
1502          *                   = 0x80 (voice mail; store sms)
1503          * msg_count = 0x00 ..0xFF
1504          */
1505         if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) {
1506             for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) {
1507                 int msgInd = msg.msgIndType & 0xff;
1508                 /*
1509                  * TS 23.040 V6.8.1 Sec 9.2.3.24.2
1510                  * bits 1 0 : basic message indication type
1511                  * bits 4 3 2 : extended message indication type
1512                  * bits 6 5 : Profile id bit 7 storage type
1513                  */
1514                 if ((msgInd == 0) || (msgInd == 0x80)) {
1515                     mIsMwi = true;
1516                     if (msgInd == 0x80) {
1517                         /* Store message because TP_UDH indicates so*/
1518                         mMwiDontStore = false;
1519                     } else if (mMwiDontStore == false) {
1520                         /* Storage bit is not set by TP_UDH
1521                          * Check for conflict
1522                          * between message storage bit in TP_UDH
1523                          * & DCS. The message shall be stored if either of
1524                          * the one indicates so.
1525                          * TS 23.040 V6.8.1 Sec 9.2.3.24.2
1526                          */
1527                         if (!((((mDataCodingScheme & 0xF0) == 0xD0)
1528                                || ((mDataCodingScheme & 0xF0) == 0xE0))
1529                                && ((mDataCodingScheme & 0x03) == 0x00))) {
1530                             /* Even DCS did not have voice mail with Storage bit
1531                              * 3GPP TS 23.038 V7.0.0 section 4
1532                              * So clear this flag*/
1533                             mMwiDontStore = true;
1534                         }
1535                     }
1536 
1537                     mVoiceMailCount = msg.msgCount & 0xff;
1538 
1539                     /*
1540                      * In the event of a conflict between message count setting
1541                      * and DCS then the Message Count in the TP-UDH shall
1542                      * override the indication in the TP-DCS. Set voice mail
1543                      * notification based on count in TP-UDH
1544                      */
1545                     if (mVoiceMailCount > 0)
1546                         mMwiSense = true;
1547                     else
1548                         mMwiSense = false;
1549 
1550                     Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd
1551                             + " Dont store = " + mMwiDontStore + " Vmail count = "
1552                             + mVoiceMailCount);
1553 
1554                     /*
1555                      * There can be only one IE for each type of message
1556                      * indication in TP_UDH. In the event they are duplicated
1557                      * last occurence will be used. Hence the for loop
1558                      */
1559                 } else {
1560                     Rlog.w(LOG_TAG, "TP_UDH fax/email/"
1561                             + "extended msg/multisubscriber profile. Msg Ind = " + msgInd);
1562                 }
1563             } // end of for
1564         } // end of if UDH
1565 
1566         switch (encodingType) {
1567         case ENCODING_UNKNOWN:
1568             mMessageBody = null;
1569             break;
1570 
1571         case ENCODING_8BIT:
1572             //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
1573             //that's stored in 8-bit unpacked format) characters.
1574             if (r.getBoolean(com.android.internal.
1575                     R.bool.config_sms_decode_gsm_8bit_data)) {
1576                 mMessageBody = p.getUserDataGSM8bit(count);
1577             } else {
1578                 mMessageBody = null;
1579             }
1580             break;
1581 
1582         case ENCODING_7BIT:
1583             mMessageBody = p.getUserDataGSM7Bit(count,
1584                     hasUserDataHeader ? mUserDataHeader.languageTable : 0,
1585                     hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
1586             break;
1587 
1588         case ENCODING_16BIT:
1589             mMessageBody = p.getUserDataUCS2(count);
1590             break;
1591 
1592         case ENCODING_KSC5601:
1593             mMessageBody = p.getUserDataKSC5601(count);
1594             break;
1595         }
1596 
1597         if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
1598 
1599         if (mMessageBody != null) {
1600             parseMessageBody();
1601         }
1602 
1603         if (!hasMessageClass) {
1604             messageClass = MessageClass.UNKNOWN;
1605         } else {
1606             switch (mDataCodingScheme & 0x3) {
1607             case 0:
1608                 messageClass = MessageClass.CLASS_0;
1609                 break;
1610             case 1:
1611                 messageClass = MessageClass.CLASS_1;
1612                 break;
1613             case 2:
1614                 messageClass = MessageClass.CLASS_2;
1615                 break;
1616             case 3:
1617                 messageClass = MessageClass.CLASS_3;
1618                 break;
1619             }
1620         }
1621     }
1622 
1623     /**
1624      * {@inheritDoc}
1625      */
1626     @Override
getMessageClass()1627     public MessageClass getMessageClass() {
1628         return messageClass;
1629     }
1630 
1631     /**
1632      * Returns true if this is a (U)SIM data download type SM.
1633      * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
1634      *
1635      * @return true if this is a USIM data download message; false otherwise
1636      */
isUsimDataDownload()1637     boolean isUsimDataDownload() {
1638         return messageClass == MessageClass.CLASS_2 &&
1639                 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
1640     }
1641 
getNumOfVoicemails()1642     public int getNumOfVoicemails() {
1643         /*
1644          * Order of priority if multiple indications are present is 1.UDH,
1645          *      2.DCS, 3.CPHS.
1646          * Voice mail count if voice mail present indication is
1647          * received
1648          *  1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040]
1649          *  2. DCS only: count is unknown mVoiceMailCount= -1
1650          *  3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700]
1651          * Voice mail clear, mVoiceMailCount = 0.
1652          */
1653         if ((!mIsMwi) && isCphsMwiMessage()) {
1654             if (mOriginatingAddress != null
1655                     && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) {
1656                 mVoiceMailCount = 0xff;
1657             } else {
1658                 mVoiceMailCount = 0;
1659             }
1660             Rlog.v(LOG_TAG, "CPHS voice mail message");
1661         }
1662         return mVoiceMailCount;
1663     }
1664 }
1665