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