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