1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.cdma.sms;
18
19import android.content.res.Resources;
20import android.telephony.SmsCbCmasInfo;
21import android.telephony.cdma.CdmaSmsCbProgramData;
22import android.telephony.cdma.CdmaSmsCbProgramResults;
23import android.text.format.Time;
24import android.util.Log;
25
26import com.android.internal.telephony.GsmAlphabet;
27import com.android.internal.telephony.IccUtils;
28import com.android.internal.telephony.SmsConstants;
29import com.android.internal.telephony.SmsHeader;
30import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
31import com.android.internal.util.BitwiseInputStream;
32import com.android.internal.util.BitwiseOutputStream;
33
34import java.util.ArrayList;
35import java.util.TimeZone;
36
37/**
38 * An object to encode and decode CDMA SMS bearer data.
39 */
40public final class BearerData {
41    private final static String LOG_TAG = "SMS";
42
43    /**
44     * Bearer Data Subparameter Identifiers
45     * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
46     * NOTE: Commented subparameter types are not implemented.
47     */
48    private final static byte SUBPARAM_MESSAGE_IDENTIFIER               = 0x00;
49    private final static byte SUBPARAM_USER_DATA                        = 0x01;
50    private final static byte SUBPARAM_USER_RESPONSE_CODE               = 0x02;
51    private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP        = 0x03;
52    private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE         = 0x04;
53    private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE         = 0x05;
54    private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE  = 0x06;
55    private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE  = 0x07;
56    private final static byte SUBPARAM_PRIORITY_INDICATOR               = 0x08;
57    private final static byte SUBPARAM_PRIVACY_INDICATOR                = 0x09;
58    private final static byte SUBPARAM_REPLY_OPTION                     = 0x0A;
59    private final static byte SUBPARAM_NUMBER_OF_MESSAGES               = 0x0B;
60    private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY        = 0x0C;
61    private final static byte SUBPARAM_LANGUAGE_INDICATOR               = 0x0D;
62    private final static byte SUBPARAM_CALLBACK_NUMBER                  = 0x0E;
63    private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE             = 0x0F;
64    //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA      = 0x10;
65    private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX            = 0x11;
66    private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA    = 0x12;
67    private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
68    private final static byte SUBPARAM_MESSAGE_STATUS                   = 0x14;
69    //private final static byte SUBPARAM_TP_FAILURE_CAUSE                 = 0x15;
70    //private final static byte SUBPARAM_ENHANCED_VMN                     = 0x16;
71    //private final static byte SUBPARAM_ENHANCED_VMN_ACK                 = 0x17;
72
73    /**
74     * Supported message types for CDMA SMS messages
75     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
76     */
77    public static final int MESSAGE_TYPE_DELIVER        = 0x01;
78    public static final int MESSAGE_TYPE_SUBMIT         = 0x02;
79    public static final int MESSAGE_TYPE_CANCELLATION   = 0x03;
80    public static final int MESSAGE_TYPE_DELIVERY_ACK   = 0x04;
81    public static final int MESSAGE_TYPE_USER_ACK       = 0x05;
82    public static final int MESSAGE_TYPE_READ_ACK       = 0x06;
83    public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07;
84    public static final int MESSAGE_TYPE_SUBMIT_REPORT  = 0x08;
85
86    public int messageType;
87
88    /**
89     * 16-bit value indicating the message ID, which increments modulo 65536.
90     * (Special rules apply for WAP-messages.)
91     * (See 3GPP2 C.S0015-B, v2, 4.5.1)
92     */
93    public int messageId;
94
95    /**
96     * Supported priority modes for CDMA SMS messages
97     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
98     */
99    public static final int PRIORITY_NORMAL        = 0x0;
100    public static final int PRIORITY_INTERACTIVE   = 0x1;
101    public static final int PRIORITY_URGENT        = 0x2;
102    public static final int PRIORITY_EMERGENCY     = 0x3;
103
104    public boolean priorityIndicatorSet = false;
105    public int priority = PRIORITY_NORMAL;
106
107    /**
108     * Supported privacy modes for CDMA SMS messages
109     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1)
110     */
111    public static final int PRIVACY_NOT_RESTRICTED = 0x0;
112    public static final int PRIVACY_RESTRICTED     = 0x1;
113    public static final int PRIVACY_CONFIDENTIAL   = 0x2;
114    public static final int PRIVACY_SECRET         = 0x3;
115
116    public boolean privacyIndicatorSet = false;
117    public int privacy = PRIVACY_NOT_RESTRICTED;
118
119    /**
120     * Supported alert priority modes for CDMA SMS messages
121     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1)
122     */
123    public static final int ALERT_DEFAULT          = 0x0;
124    public static final int ALERT_LOW_PRIO         = 0x1;
125    public static final int ALERT_MEDIUM_PRIO      = 0x2;
126    public static final int ALERT_HIGH_PRIO        = 0x3;
127
128    public boolean alertIndicatorSet = false;
129    public int alert = ALERT_DEFAULT;
130
131    /**
132     * Supported display modes for CDMA SMS messages.  Display mode is
133     * a 2-bit value used to indicate to the mobile station when to
134     * display the received message.  (See 3GPP2 C.S0015-B, v2,
135     * 4.5.16)
136     */
137    public static final int DISPLAY_MODE_IMMEDIATE      = 0x0;
138    public static final int DISPLAY_MODE_DEFAULT        = 0x1;
139    public static final int DISPLAY_MODE_USER           = 0x2;
140
141    public boolean displayModeSet = false;
142    public int displayMode = DISPLAY_MODE_DEFAULT;
143
144    /**
145     * Language Indicator values.  NOTE: the spec (3GPP2 C.S0015-B,
146     * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
147     * refers to C.R1001-D but that reference has been crossed out.
148     * It would seem reasonable to assume the values from C.R1001-F
149     * (table 9.2-1) are to be used instead.
150     */
151    public static final int LANGUAGE_UNKNOWN  = 0x00;
152    public static final int LANGUAGE_ENGLISH  = 0x01;
153    public static final int LANGUAGE_FRENCH   = 0x02;
154    public static final int LANGUAGE_SPANISH  = 0x03;
155    public static final int LANGUAGE_JAPANESE = 0x04;
156    public static final int LANGUAGE_KOREAN   = 0x05;
157    public static final int LANGUAGE_CHINESE  = 0x06;
158    public static final int LANGUAGE_HEBREW   = 0x07;
159
160    public boolean languageIndicatorSet = false;
161    public int language = LANGUAGE_UNKNOWN;
162
163    /**
164     * SMS Message Status Codes.  The first component of the Message
165     * status indicates if an error has occurred and whether the error
166     * is considered permanent or temporary.  The second component of
167     * the Message status indicates the cause of the error (if any).
168     * (See 3GPP2 C.S0015-B, v2.0, 4.5.21)
169     */
170    /* no-error codes */
171    public static final int ERROR_NONE                   = 0x00;
172    public static final int STATUS_ACCEPTED              = 0x00;
173    public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01;
174    public static final int STATUS_DELIVERED             = 0x02;
175    public static final int STATUS_CANCELLED             = 0x03;
176    /* temporary-error and permanent-error codes */
177    public static final int ERROR_TEMPORARY              = 0x02;
178    public static final int STATUS_NETWORK_CONGESTION    = 0x04;
179    public static final int STATUS_NETWORK_ERROR         = 0x05;
180    public static final int STATUS_UNKNOWN_ERROR         = 0x1F;
181    /* permanent-error codes */
182    public static final int ERROR_PERMANENT              = 0x03;
183    public static final int STATUS_CANCEL_FAILED         = 0x06;
184    public static final int STATUS_BLOCKED_DESTINATION   = 0x07;
185    public static final int STATUS_TEXT_TOO_LONG         = 0x08;
186    public static final int STATUS_DUPLICATE_MESSAGE     = 0x09;
187    public static final int STATUS_INVALID_DESTINATION   = 0x0A;
188    public static final int STATUS_MESSAGE_EXPIRED       = 0x0D;
189    /* undefined-status codes */
190    public static final int ERROR_UNDEFINED              = 0xFF;
191    public static final int STATUS_UNDEFINED             = 0xFF;
192
193    public boolean messageStatusSet = false;
194    public int errorClass = ERROR_UNDEFINED;
195    public int messageStatus = STATUS_UNDEFINED;
196
197    /**
198     * 1-bit value that indicates whether a User Data Header (UDH) is present.
199     * (See 3GPP2 C.S0015-B, v2, 4.5.1)
200     *
201     * NOTE: during encoding, this value will be set based on the
202     * presence of a UDH in the structured data, any existing setting
203     * will be overwritten.
204     */
205    public boolean hasUserDataHeader;
206
207    /**
208     * provides the information for the user data
209     * (e.g. padding bits, user data, user data header, etc)
210     * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
211     */
212    public UserData userData;
213
214    /**
215     * The User Response Code subparameter is used in the SMS User
216     * Acknowledgment Message to respond to previously received short
217     * messages. This message center-specific element carries the
218     * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2,
219     * 4.5.3)
220     */
221    public boolean userResponseCodeSet = false;
222    public int userResponseCode;
223
224    /**
225     * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
226     */
227    public static class TimeStamp extends Time {
228
229        public TimeStamp() {
230            super(TimeZone.getDefault().getID());   // 3GPP2 timestamps use the local timezone
231        }
232
233        public static TimeStamp fromByteArray(byte[] data) {
234            TimeStamp ts = new TimeStamp();
235            // C.S0015-B v2.0, 4.5.4: range is 1996-2095
236            int year = IccUtils.cdmaBcdByteToInt(data[0]);
237            if (year > 99 || year < 0) return null;
238            ts.year = year >= 96 ? year + 1900 : year + 2000;
239            int month = IccUtils.cdmaBcdByteToInt(data[1]);
240            if (month < 1 || month > 12) return null;
241            ts.month = month - 1;
242            int day = IccUtils.cdmaBcdByteToInt(data[2]);
243            if (day < 1 || day > 31) return null;
244            ts.monthDay = day;
245            int hour = IccUtils.cdmaBcdByteToInt(data[3]);
246            if (hour < 0 || hour > 23) return null;
247            ts.hour = hour;
248            int minute = IccUtils.cdmaBcdByteToInt(data[4]);
249            if (minute < 0 || minute > 59) return null;
250            ts.minute = minute;
251            int second = IccUtils.cdmaBcdByteToInt(data[5]);
252            if (second < 0 || second > 59) return null;
253            ts.second = second;
254            return ts;
255        }
256
257        @Override
258        public String toString() {
259            StringBuilder builder = new StringBuilder();
260            builder.append("TimeStamp ");
261            builder.append("{ year=" + year);
262            builder.append(", month=" + month);
263            builder.append(", day=" + monthDay);
264            builder.append(", hour=" + hour);
265            builder.append(", minute=" + minute);
266            builder.append(", second=" + second);
267            builder.append(" }");
268            return builder.toString();
269        }
270    }
271
272    public TimeStamp msgCenterTimeStamp;
273    public TimeStamp validityPeriodAbsolute;
274    public TimeStamp deferredDeliveryTimeAbsolute;
275
276    /**
277     * Relative time is specified as one byte, the value of which
278     * falls into a series of ranges, as specified below.  The idea is
279     * that shorter time intervals allow greater precision -- the
280     * value means minutes from zero until the MINS_LIMIT (inclusive),
281     * upon which it means hours until the HOURS_LIMIT, and so
282     * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1)
283     */
284    public static final int RELATIVE_TIME_MINS_LIMIT      = 143;
285    public static final int RELATIVE_TIME_HOURS_LIMIT     = 167;
286    public static final int RELATIVE_TIME_DAYS_LIMIT      = 196;
287    public static final int RELATIVE_TIME_WEEKS_LIMIT     = 244;
288    public static final int RELATIVE_TIME_INDEFINITE      = 245;
289    public static final int RELATIVE_TIME_NOW             = 246;
290    public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247;
291    public static final int RELATIVE_TIME_RESERVED        = 248;
292
293    public boolean validityPeriodRelativeSet;
294    public int validityPeriodRelative;
295    public boolean deferredDeliveryTimeRelativeSet;
296    public int deferredDeliveryTimeRelative;
297
298    /**
299     * The Reply Option subparameter contains 1-bit values which
300     * indicate whether SMS acknowledgment is requested or not.  (See
301     * 3GPP2 C.S0015-B, v2, 4.5.11)
302     */
303    public boolean userAckReq;
304    public boolean deliveryAckReq;
305    public boolean readAckReq;
306    public boolean reportReq;
307
308    /**
309     * The Number of Messages subparameter (8-bit value) is a decimal
310     * number in the 0 to 99 range representing the number of messages
311     * stored at the Voice Mail System. This element is used by the
312     * Voice Mail Notification service.  (See 3GPP2 C.S0015-B, v2,
313     * 4.5.12)
314     */
315    public int numberOfMessages;
316
317    /**
318     * The Message Deposit Index subparameter is assigned by the
319     * message center as a unique index to the contents of the User
320     * Data subparameter in each message sent to a particular mobile
321     * station. The mobile station, when replying to a previously
322     * received short message which included a Message Deposit Index
323     * subparameter, may include the Message Deposit Index of the
324     * received message to indicate to the message center that the
325     * original contents of the message are to be included in the
326     * reply.  (See 3GPP2 C.S0015-B, v2, 4.5.18)
327     */
328    public int depositIndex;
329
330    /**
331     * 4-bit or 8-bit value that indicates the number to be dialed in reply to a
332     * received SMS message.
333     * (See 3GPP2 C.S0015-B, v2, 4.5.15)
334     */
335    public CdmaSmsAddress callbackNumber;
336
337    /**
338     * CMAS warning notification information.
339     * @see #decodeCmasUserData(BearerData, int)
340     */
341    public SmsCbCmasInfo cmasWarningInfo;
342
343    /**
344     * The Service Category Program Data subparameter is used to enable and disable
345     * SMS broadcast service categories to display. If this subparameter is present,
346     * this field will contain a list of one or more
347     * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
348     * operation(s) to perform.
349     */
350    public ArrayList<CdmaSmsCbProgramData> serviceCategoryProgramData;
351
352    /**
353     * The Service Category Program Results subparameter informs the message center
354     * of the results of a Service Category Program Data request.
355     */
356    public ArrayList<CdmaSmsCbProgramResults> serviceCategoryProgramResults;
357
358
359    private static class CodingException extends Exception {
360        public CodingException(String s) {
361            super(s);
362        }
363    }
364
365    /**
366     * Returns the language indicator as a two-character ISO 639 string.
367     * @return a two character ISO 639 language code
368     */
369    public String getLanguage() {
370        return getLanguageCodeForValue(language);
371    }
372
373    /**
374     * Converts a CDMA language indicator value to an ISO 639 two character language code.
375     * @param languageValue the CDMA language value to convert
376     * @return the two character ISO 639 language code for the specified value, or null if unknown
377     */
378    private static String getLanguageCodeForValue(int languageValue) {
379        switch (languageValue) {
380            case LANGUAGE_ENGLISH:
381                return "en";
382
383            case LANGUAGE_FRENCH:
384                return "fr";
385
386            case LANGUAGE_SPANISH:
387                return "es";
388
389            case LANGUAGE_JAPANESE:
390                return "ja";
391
392            case LANGUAGE_KOREAN:
393                return "ko";
394
395            case LANGUAGE_CHINESE:
396                return "zh";
397
398            case LANGUAGE_HEBREW:
399                return "he";
400
401            default:
402                return null;
403        }
404    }
405
406    @Override
407    public String toString() {
408        StringBuilder builder = new StringBuilder();
409        builder.append("BearerData ");
410        builder.append("{ messageType=" + messageType);
411        builder.append(", messageId=" + (int)messageId);
412        builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
413        builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
414        builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
415        builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
416        builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
417        builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
418        builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
419        builder.append(", msgCenterTimeStamp=" +
420                ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset"));
421        builder.append(", validityPeriodAbsolute=" +
422                ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset"));
423        builder.append(", validityPeriodRelative=" +
424                ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset"));
425        builder.append(", deferredDeliveryTimeAbsolute=" +
426                ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset"));
427        builder.append(", deferredDeliveryTimeRelative=" +
428                ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset"));
429        builder.append(", userAckReq=" + userAckReq);
430        builder.append(", deliveryAckReq=" + deliveryAckReq);
431        builder.append(", readAckReq=" + readAckReq);
432        builder.append(", reportReq=" + reportReq);
433        builder.append(", numberOfMessages=" + numberOfMessages);
434        builder.append(", callbackNumber=" + callbackNumber);
435        builder.append(", depositIndex=" + depositIndex);
436        builder.append(", hasUserDataHeader=" + hasUserDataHeader);
437        builder.append(", userData=" + userData);
438        builder.append(" }");
439        return builder.toString();
440    }
441
442    private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream)
443        throws BitwiseOutputStream.AccessException
444    {
445        outStream.write(8, 3);
446        outStream.write(4, bData.messageType);
447        outStream.write(8, bData.messageId >> 8);
448        outStream.write(8, bData.messageId);
449        outStream.write(1, bData.hasUserDataHeader ? 1 : 0);
450        outStream.skip(3);
451    }
452
453    private static int countAsciiSeptets(CharSequence msg, boolean force) {
454        int msgLen = msg.length();
455        if (force) return msgLen;
456        for (int i = 0; i < msgLen; i++) {
457            if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) {
458                return -1;
459            }
460        }
461        return msgLen;
462    }
463
464    /**
465     * Calculate the message text encoding length, fragmentation, and other details.
466     *
467     * @param msg message text
468     * @param force7BitEncoding ignore (but still count) illegal characters if true
469     * @return septet count, or -1 on failure
470     */
471    public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
472            boolean force7BitEncoding) {
473        TextEncodingDetails ted;
474        int septets = countAsciiSeptets(msg, force7BitEncoding);
475        if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) {
476            ted = new TextEncodingDetails();
477            ted.msgCount = 1;
478            ted.codeUnitCount = septets;
479            ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
480            ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
481        } else {
482            ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
483                    msg, force7BitEncoding);
484            if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
485                // We don't support single-segment EMS, so calculate for 16-bit
486                // TODO: Consider supporting single-segment EMS
487                ted.codeUnitCount = msg.length();
488                int octets = ted.codeUnitCount * 2;
489                if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
490                    ted.msgCount = (octets + (SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
491                            SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
492                    ted.codeUnitsRemaining = ((ted.msgCount *
493                            SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
494                } else {
495                    ted.msgCount = 1;
496                    ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets)/2;
497                }
498                ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
499            }
500        }
501        return ted;
502    }
503
504    private static byte[] encode7bitAscii(String msg, boolean force)
505        throws CodingException
506    {
507        try {
508            BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length());
509            int msgLen = msg.length();
510            for (int i = 0; i < msgLen; i++) {
511                int charCode = UserData.charToAscii.get(msg.charAt(i), -1);
512                if (charCode == -1) {
513                    if (force) {
514                        outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR);
515                    } else {
516                        throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")");
517                    }
518                } else {
519                    outStream.write(7, charCode);
520                }
521            }
522            return outStream.toByteArray();
523        } catch (BitwiseOutputStream.AccessException ex) {
524            throw new CodingException("7bit ASCII encode failed: " + ex);
525        }
526    }
527
528    private static byte[] encodeUtf16(String msg)
529        throws CodingException
530    {
531        try {
532            return msg.getBytes("utf-16be");
533        } catch (java.io.UnsupportedEncodingException ex) {
534            throw new CodingException("UTF-16 encode failed: " + ex);
535        }
536    }
537
538    private static class Gsm7bitCodingResult {
539        int septets;
540        byte[] data;
541    }
542
543    private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
544        throws CodingException
545    {
546        try {
547            /*
548             * TODO(cleanup): It would be nice if GsmAlphabet provided
549             * an option to produce just the data without prepending
550             * the septet count, as this function is really just a
551             * wrapper to strip that off.  Not to mention that the
552             * septet count is generally known prior to invocation of
553             * the encoder.  Note that it cannot be derived from the
554             * resulting array length, since that cannot distinguish
555             * if the last contains either 1 or 8 valid bits.
556             *
557             * TODO(cleanup): The BitwiseXStreams could also be
558             * extended with byte-wise reversed endianness read/write
559             * routines to allow a corresponding implementation of
560             * stringToGsm7BitPacked, and potentially directly support
561             * access to the main bitwise stream from encode/decode.
562             */
563            byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
564            Gsm7bitCodingResult result = new Gsm7bitCodingResult();
565            result.data = new byte[fullData.length - 1];
566            System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
567            result.septets = fullData[0] & 0x00FF;
568            return result;
569        } catch (com.android.internal.telephony.EncodeException ex) {
570            throw new CodingException("7bit GSM encode failed: " + ex);
571        }
572    }
573
574    private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
575        throws CodingException
576    {
577        int udhBytes = udhData.length + 1;  // Add length octet.
578        int udhSeptets = ((udhBytes * 8) + 6) / 7;
579        Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
580        uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
581        uData.msgEncodingSet = true;
582        uData.numFields = gcr.septets;
583        uData.payload = gcr.data;
584        uData.payload[0] = (byte)udhData.length;
585        System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
586    }
587
588    private static void encode16bitEms(UserData uData, byte[] udhData)
589        throws CodingException
590    {
591        byte[] payload = encodeUtf16(uData.payloadStr);
592        int udhBytes = udhData.length + 1;  // Add length octet.
593        int udhCodeUnits = (udhBytes + 1) / 2;
594        int payloadCodeUnits = payload.length / 2;
595        uData.msgEncoding = UserData.ENCODING_UNICODE_16;
596        uData.msgEncodingSet = true;
597        uData.numFields = udhCodeUnits + payloadCodeUnits;
598        uData.payload = new byte[uData.numFields * 2];
599        uData.payload[0] = (byte)udhData.length;
600        System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
601        System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
602    }
603
604    private static void encodeEmsUserDataPayload(UserData uData)
605        throws CodingException
606    {
607        byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
608        if (uData.msgEncodingSet) {
609            if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
610                encode7bitEms(uData, headerData, true);
611            } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
612                encode16bitEms(uData, headerData);
613            } else {
614                throw new CodingException("unsupported EMS user data encoding (" +
615                                          uData.msgEncoding + ")");
616            }
617        } else {
618            try {
619                encode7bitEms(uData, headerData, false);
620            } catch (CodingException ex) {
621                encode16bitEms(uData, headerData);
622            }
623        }
624    }
625
626    private static void encodeUserDataPayload(UserData uData)
627        throws CodingException
628    {
629        if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
630            Log.e(LOG_TAG, "user data with null payloadStr");
631            uData.payloadStr = "";
632        }
633
634        if (uData.userDataHeader != null) {
635            encodeEmsUserDataPayload(uData);
636            return;
637        }
638
639        if (uData.msgEncodingSet) {
640            if (uData.msgEncoding == UserData.ENCODING_OCTET) {
641                if (uData.payload == null) {
642                    Log.e(LOG_TAG, "user data with octet encoding but null payload");
643                    uData.payload = new byte[0];
644                    uData.numFields = 0;
645                } else {
646                    uData.numFields = uData.payload.length;
647                }
648            } else {
649                if (uData.payloadStr == null) {
650                    Log.e(LOG_TAG, "non-octet user data with null payloadStr");
651                    uData.payloadStr = "";
652                }
653                if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
654                    Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
655                    uData.payload = gcr.data;
656                    uData.numFields = gcr.septets;
657                } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
658                    uData.payload = encode7bitAscii(uData.payloadStr, true);
659                    uData.numFields = uData.payloadStr.length();
660                } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
661                    uData.payload = encodeUtf16(uData.payloadStr);
662                    uData.numFields = uData.payloadStr.length();
663                } else {
664                    throw new CodingException("unsupported user data encoding (" +
665                                              uData.msgEncoding + ")");
666                }
667            }
668        } else {
669            try {
670                uData.payload = encode7bitAscii(uData.payloadStr, false);
671                uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
672            } catch (CodingException ex) {
673                uData.payload = encodeUtf16(uData.payloadStr);
674                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
675            }
676            uData.numFields = uData.payloadStr.length();
677            uData.msgEncodingSet = true;
678        }
679    }
680
681    private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
682        throws BitwiseOutputStream.AccessException, CodingException
683    {
684        /*
685         * TODO(cleanup): Do we really need to set userData.payload as
686         * a side effect of encoding?  If not, we could avoid data
687         * copies by passing outStream directly.
688         */
689        encodeUserDataPayload(bData.userData);
690        bData.hasUserDataHeader = bData.userData.userDataHeader != null;
691
692        if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) {
693            throw new CodingException("encoded user data too large (" +
694                                      bData.userData.payload.length +
695                                      " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
696        }
697
698        /*
699         * TODO(cleanup): figure out what the right answer is WRT paddingBits field
700         *
701         *   userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
702         *   userData.paddingBits = 0; // XXX this seems better, but why?
703         *
704         */
705        int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
706        int paramBits = dataBits + 13;
707        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
708            (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
709            paramBits += 8;
710        }
711        int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
712        int paddingBits = (paramBytes * 8) - paramBits;
713        outStream.write(8, paramBytes);
714        outStream.write(5, bData.userData.msgEncoding);
715        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
716            (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
717            outStream.write(8, bData.userData.msgType);
718        }
719        outStream.write(8, bData.userData.numFields);
720        outStream.writeByteArray(dataBits, bData.userData.payload);
721        if (paddingBits > 0) outStream.write(paddingBits, 0);
722    }
723
724    private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)
725        throws BitwiseOutputStream.AccessException
726    {
727        outStream.write(8, 1);
728        outStream.write(1, bData.userAckReq     ? 1 : 0);
729        outStream.write(1, bData.deliveryAckReq ? 1 : 0);
730        outStream.write(1, bData.readAckReq     ? 1 : 0);
731        outStream.write(1, bData.reportReq      ? 1 : 0);
732        outStream.write(4, 0);
733    }
734
735    private static byte[] encodeDtmfSmsAddress(String address) {
736        int digits = address.length();
737        int dataBits = digits * 4;
738        int dataBytes = (dataBits / 8);
739        dataBytes += (dataBits % 8) > 0 ? 1 : 0;
740        byte[] rawData = new byte[dataBytes];
741        for (int i = 0; i < digits; i++) {
742            char c = address.charAt(i);
743            int val = 0;
744            if ((c >= '1') && (c <= '9')) val = c - '0';
745            else if (c == '0') val = 10;
746            else if (c == '*') val = 11;
747            else if (c == '#') val = 12;
748            else return null;
749            rawData[i / 2] |= val << (4 - ((i % 2) * 4));
750        }
751        return rawData;
752    }
753
754    /*
755     * TODO(cleanup): CdmaSmsAddress encoding should make use of
756     * CdmaSmsAddress.parse provided that DTMF encoding is unified,
757     * and the difference in 4-bit vs. 8-bit is resolved.
758     */
759
760    private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
761        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
762            try {
763                addr.origBytes = addr.address.getBytes("US-ASCII");
764            } catch (java.io.UnsupportedEncodingException ex) {
765                throw new CodingException("invalid SMS address, cannot convert to ASCII");
766            }
767        } else {
768            addr.origBytes = encodeDtmfSmsAddress(addr.address);
769        }
770    }
771
772    private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)
773        throws BitwiseOutputStream.AccessException, CodingException
774    {
775        CdmaSmsAddress addr = bData.callbackNumber;
776        encodeCdmaSmsAddress(addr);
777        int paramBits = 9;
778        int dataBits = 0;
779        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
780            paramBits += 7;
781            dataBits = addr.numberOfDigits * 8;
782        } else {
783            dataBits = addr.numberOfDigits * 4;
784        }
785        paramBits += dataBits;
786        int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
787        int paddingBits = (paramBytes * 8) - paramBits;
788        outStream.write(8, paramBytes);
789        outStream.write(1, addr.digitMode);
790        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
791            outStream.write(3, addr.ton);
792            outStream.write(4, addr.numberPlan);
793        }
794        outStream.write(8, addr.numberOfDigits);
795        outStream.writeByteArray(dataBits, addr.origBytes);
796        if (paddingBits > 0) outStream.write(paddingBits, 0);
797    }
798
799    private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)
800        throws BitwiseOutputStream.AccessException
801    {
802        outStream.write(8, 1);
803        outStream.write(2, bData.errorClass);
804        outStream.write(6, bData.messageStatus);
805    }
806
807    private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)
808        throws BitwiseOutputStream.AccessException
809    {
810        outStream.write(8, 1);
811        outStream.write(8, bData.numberOfMessages);
812    }
813
814    private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)
815        throws BitwiseOutputStream.AccessException
816    {
817        outStream.write(8, 1);
818        outStream.write(8, bData.validityPeriodRelative);
819    }
820
821    private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)
822        throws BitwiseOutputStream.AccessException
823    {
824        outStream.write(8, 1);
825        outStream.write(2, bData.privacy);
826        outStream.skip(6);
827    }
828
829    private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)
830        throws BitwiseOutputStream.AccessException
831    {
832        outStream.write(8, 1);
833        outStream.write(8, bData.language);
834    }
835
836    private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)
837        throws BitwiseOutputStream.AccessException
838    {
839        outStream.write(8, 1);
840        outStream.write(2, bData.displayMode);
841        outStream.skip(6);
842    }
843
844    private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)
845        throws BitwiseOutputStream.AccessException
846    {
847        outStream.write(8, 1);
848        outStream.write(2, bData.priority);
849        outStream.skip(6);
850    }
851
852    private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)
853        throws BitwiseOutputStream.AccessException
854    {
855        outStream.write(8, 1);
856        outStream.write(2, bData.alert);
857        outStream.skip(6);
858    }
859
860    private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream)
861        throws BitwiseOutputStream.AccessException
862    {
863        ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults;
864        outStream.write(8, (results.size() * 4));   // 4 octets per program result
865        for (CdmaSmsCbProgramResults result : results) {
866            int category = result.getCategory();
867            outStream.write(8, category >> 8);
868            outStream.write(8, category);
869            outStream.write(8, result.getLanguage());
870            outStream.write(4, result.getCategoryResult());
871            outStream.skip(4);
872        }
873    }
874
875    /**
876     * Create serialized representation for BearerData object.
877     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
878     *
879     * @param bData an instance of BearerData.
880     *
881     * @return byte array of raw encoded SMS bearer data.
882     */
883    public static byte[] encode(BearerData bData) {
884        bData.hasUserDataHeader = ((bData.userData != null) &&
885                (bData.userData.userDataHeader != null));
886        try {
887            BitwiseOutputStream outStream = new BitwiseOutputStream(200);
888            outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
889            encodeMessageId(bData, outStream);
890            if (bData.userData != null) {
891                outStream.write(8, SUBPARAM_USER_DATA);
892                encodeUserData(bData, outStream);
893            }
894            if (bData.callbackNumber != null) {
895                outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
896                encodeCallbackNumber(bData, outStream);
897            }
898            if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
899                outStream.write(8, SUBPARAM_REPLY_OPTION);
900                encodeReplyOption(bData, outStream);
901            }
902            if (bData.numberOfMessages != 0) {
903                outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
904                encodeMsgCount(bData, outStream);
905            }
906            if (bData.validityPeriodRelativeSet) {
907                outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
908                encodeValidityPeriodRel(bData, outStream);
909            }
910            if (bData.privacyIndicatorSet) {
911                outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
912                encodePrivacyIndicator(bData, outStream);
913            }
914            if (bData.languageIndicatorSet) {
915                outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
916                encodeLanguageIndicator(bData, outStream);
917            }
918            if (bData.displayModeSet) {
919                outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
920                encodeDisplayMode(bData, outStream);
921            }
922            if (bData.priorityIndicatorSet) {
923                outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
924                encodePriorityIndicator(bData, outStream);
925            }
926            if (bData.alertIndicatorSet) {
927                outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
928                encodeMsgDeliveryAlert(bData, outStream);
929            }
930            if (bData.messageStatusSet) {
931                outStream.write(8, SUBPARAM_MESSAGE_STATUS);
932                encodeMsgStatus(bData, outStream);
933            }
934            if (bData.serviceCategoryProgramResults != null) {
935                outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS);
936                encodeScpResults(bData, outStream);
937            }
938            return outStream.toByteArray();
939        } catch (BitwiseOutputStream.AccessException ex) {
940            Log.e(LOG_TAG, "BearerData encode failed: " + ex);
941        } catch (CodingException ex) {
942            Log.e(LOG_TAG, "BearerData encode failed: " + ex);
943        }
944        return null;
945   }
946
947    private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
948        throws BitwiseInputStream.AccessException, CodingException
949    {
950        final int EXPECTED_PARAM_SIZE = 3 * 8;
951        boolean decodeSuccess = false;
952        int paramBits = inStream.read(8) * 8;
953        if (paramBits >= EXPECTED_PARAM_SIZE) {
954            paramBits -= EXPECTED_PARAM_SIZE;
955            decodeSuccess = true;
956            bData.messageType = inStream.read(4);
957            bData.messageId = inStream.read(8) << 8;
958            bData.messageId |= inStream.read(8);
959            bData.hasUserDataHeader = (inStream.read(1) == 1);
960            inStream.skip(3);
961        }
962        if ((! decodeSuccess) || (paramBits > 0)) {
963            Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
964                      (decodeSuccess ? "succeeded" : "failed") +
965                      " (extra bits = " + paramBits + ")");
966        }
967        inStream.skip(paramBits);
968        return decodeSuccess;
969    }
970
971    private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
972        throws BitwiseInputStream.AccessException
973    {
974        int paramBits = inStream.read(8) * 8;
975        bData.userData = new UserData();
976        bData.userData.msgEncoding = inStream.read(5);
977        bData.userData.msgEncodingSet = true;
978        bData.userData.msgType = 0;
979        int consumedBits = 5;
980        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
981            (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
982            bData.userData.msgType = inStream.read(8);
983            consumedBits += 8;
984        }
985        bData.userData.numFields = inStream.read(8);
986        consumedBits += 8;
987        int dataBits = paramBits - consumedBits;
988        bData.userData.payload = inStream.readByteArray(dataBits);
989        return true;
990    }
991
992    private static String decodeUtf8(byte[] data, int offset, int numFields)
993        throws CodingException
994    {
995        return decodeCharset(data, offset, numFields, 1, "UTF-8");
996    }
997
998    private static String decodeUtf16(byte[] data, int offset, int numFields)
999        throws CodingException
1000    {
1001        // Subtract header and possible padding byte (at end) from num fields.
1002        int padding = offset % 2;
1003        numFields -= (offset + padding) / 2;
1004        return decodeCharset(data, offset, numFields, 2, "utf-16be");
1005    }
1006
1007    private static String decodeCharset(byte[] data, int offset, int numFields, int width,
1008            String charset) throws CodingException
1009    {
1010        if (numFields < 0 || (numFields * width + offset) > data.length) {
1011            // Try to decode the max number of characters in payload
1012            int padding = offset % width;
1013            int maxNumFields = (data.length - offset - padding) / width;
1014            if (maxNumFields < 0) {
1015                throw new CodingException(charset + " decode failed: offset out of range");
1016            }
1017            Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
1018                    + numFields + " data.length = " + data.length + " maxNumFields = "
1019                    + maxNumFields);
1020            numFields = maxNumFields;
1021        }
1022        try {
1023            return new String(data, offset, numFields * width, charset);
1024        } catch (java.io.UnsupportedEncodingException ex) {
1025            throw new CodingException(charset + " decode failed: " + ex);
1026        }
1027    }
1028
1029    private static String decode7bitAscii(byte[] data, int offset, int numFields)
1030        throws CodingException
1031    {
1032        try {
1033            offset *= 8;
1034            StringBuffer strBuf = new StringBuffer(numFields);
1035            BitwiseInputStream inStream = new BitwiseInputStream(data);
1036            int wantedBits = (offset * 8) + (numFields * 7);
1037            if (inStream.available() < wantedBits) {
1038                throw new CodingException("insufficient data (wanted " + wantedBits +
1039                                          " bits, but only have " + inStream.available() + ")");
1040            }
1041            inStream.skip(offset);
1042            for (int i = 0; i < numFields; i++) {
1043                int charCode = inStream.read(7);
1044                if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
1045                        (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
1046                    strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
1047                } else if (charCode == UserData.ASCII_NL_INDEX) {
1048                    strBuf.append('\n');
1049                } else if (charCode == UserData.ASCII_CR_INDEX) {
1050                    strBuf.append('\r');
1051                } else {
1052                    /* For other charCodes, they are unprintable, and so simply use SPACE. */
1053                    strBuf.append(' ');
1054                }
1055            }
1056            return strBuf.toString();
1057        } catch (BitwiseInputStream.AccessException ex) {
1058            throw new CodingException("7bit ASCII decode failed: " + ex);
1059        }
1060    }
1061
1062    private static String decode7bitGsm(byte[] data, int offset, int numFields)
1063        throws CodingException
1064    {
1065        // Start reading from the next 7-bit aligned boundary after offset.
1066        int offsetBits = offset * 8;
1067        int offsetSeptets = (offsetBits + 6) / 7;
1068        numFields -= offsetSeptets;
1069        int paddingBits = (offsetSeptets * 7) - offsetBits;
1070        String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
1071                0, 0);
1072        if (result == null) {
1073            throw new CodingException("7bit GSM decoding failed");
1074        }
1075        return result;
1076    }
1077
1078    private static String decodeLatin(byte[] data, int offset, int numFields)
1079        throws CodingException
1080    {
1081        return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
1082    }
1083
1084    private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
1085        throws CodingException
1086    {
1087        int offset = 0;
1088        if (hasUserDataHeader) {
1089            int udhLen = userData.payload[0] & 0x00FF;
1090            offset += udhLen + 1;
1091            byte[] headerData = new byte[udhLen];
1092            System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
1093            userData.userDataHeader = SmsHeader.fromByteArray(headerData);
1094        }
1095        switch (userData.msgEncoding) {
1096        case UserData.ENCODING_OCTET:
1097            /*
1098            *  Octet decoding depends on the carrier service.
1099            */
1100            boolean decodingtypeUTF8 = Resources.getSystem()
1101                    .getBoolean(com.android.internal.R.bool.config_sms_utf8_support);
1102
1103            // Strip off any padding bytes, meaning any differences between the length of the
1104            // array and the target length specified by numFields.  This is to avoid any
1105            // confusion by code elsewhere that only considers the payload array length.
1106            byte[] payload = new byte[userData.numFields];
1107            int copyLen = userData.numFields < userData.payload.length
1108                    ? userData.numFields : userData.payload.length;
1109
1110            System.arraycopy(userData.payload, 0, payload, 0, copyLen);
1111            userData.payload = payload;
1112
1113            if (!decodingtypeUTF8) {
1114                // There are many devices in the market that send 8bit text sms (latin encoded) as
1115                // octet encoded.
1116                userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
1117            } else {
1118                userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
1119            }
1120            break;
1121
1122        case UserData.ENCODING_IA5:
1123        case UserData.ENCODING_7BIT_ASCII:
1124            userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
1125            break;
1126        case UserData.ENCODING_UNICODE_16:
1127            userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
1128            break;
1129        case UserData.ENCODING_GSM_7BIT_ALPHABET:
1130            userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
1131            break;
1132        case UserData.ENCODING_LATIN:
1133            userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
1134            break;
1135        default:
1136            throw new CodingException("unsupported user data encoding ("
1137                                      + userData.msgEncoding + ")");
1138        }
1139    }
1140
1141    /**
1142     * IS-91 Voice Mail message decoding
1143     * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
1144     * (For character encodings, see TIA/EIA/IS-91, Annex B)
1145     *
1146     * Protocol Summary: The user data payload may contain 3-14
1147     * characters.  The first two characters are parsed as a number
1148     * and indicate the number of voicemails.  The third character is
1149     * either a SPACE or '!' to indicate normal or urgent priority,
1150     * respectively.  Any following characters are treated as normal
1151     * text user data payload.
1152     *
1153     * Note that the characters encoding is 6-bit packed.
1154     */
1155    private static void decodeIs91VoicemailStatus(BearerData bData)
1156        throws BitwiseInputStream.AccessException, CodingException
1157    {
1158        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
1159        int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
1160        int numFields = bData.userData.numFields;
1161        if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
1162            throw new CodingException("IS-91 voicemail status decoding failed");
1163        }
1164        try {
1165            StringBuffer strbuf = new StringBuffer(dataLen);
1166            while (inStream.available() >= 6) {
1167                strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
1168            }
1169            String data = strbuf.toString();
1170            bData.numberOfMessages = Integer.parseInt(data.substring(0, 2));
1171            char prioCode = data.charAt(2);
1172            if (prioCode == ' ') {
1173                bData.priority = PRIORITY_NORMAL;
1174            } else if (prioCode == '!') {
1175                bData.priority = PRIORITY_URGENT;
1176            } else {
1177                throw new CodingException("IS-91 voicemail status decoding failed: " +
1178                        "illegal priority setting (" + prioCode + ")");
1179            }
1180            bData.priorityIndicatorSet = true;
1181            bData.userData.payloadStr = data.substring(3, numFields - 3);
1182       } catch (java.lang.NumberFormatException ex) {
1183            throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
1184        } catch (java.lang.IndexOutOfBoundsException ex) {
1185            throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
1186        }
1187    }
1188
1189    /**
1190     * IS-91 Short Message decoding
1191     * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
1192     * (For character encodings, see TIA/EIA/IS-91, Annex B)
1193     *
1194     * Protocol Summary: The user data payload may contain 1-14
1195     * characters, which are treated as normal text user data payload.
1196     * Note that the characters encoding is 6-bit packed.
1197     */
1198    private static void decodeIs91ShortMessage(BearerData bData)
1199        throws BitwiseInputStream.AccessException, CodingException
1200    {
1201        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
1202        int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
1203        int numFields = bData.userData.numFields;
1204        // dataLen may be > 14 characters due to octet padding
1205        if ((numFields > 14) || (dataLen < numFields)) {
1206            throw new CodingException("IS-91 short message decoding failed");
1207        }
1208        StringBuffer strbuf = new StringBuffer(dataLen);
1209        for (int i = 0; i < numFields; i++) {
1210            strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
1211        }
1212        bData.userData.payloadStr = strbuf.toString();
1213    }
1214
1215    /**
1216     * IS-91 CLI message (callback number) decoding
1217     * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
1218     *
1219     * Protocol Summary: The data payload may contain 1-32 digits,
1220     * encoded using standard 4-bit DTMF, which are treated as a
1221     * callback number.
1222     */
1223    private static void decodeIs91Cli(BearerData bData) throws CodingException {
1224        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
1225        int dataLen = inStream.available() / 4;  // 4-bit packed DTMF digit encoding.
1226        int numFields = bData.userData.numFields;
1227        if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
1228            throw new CodingException("IS-91 voicemail status decoding failed");
1229        }
1230        CdmaSmsAddress addr = new CdmaSmsAddress();
1231        addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
1232        addr.origBytes = bData.userData.payload;
1233        addr.numberOfDigits = (byte)numFields;
1234        decodeSmsAddress(addr);
1235        bData.callbackNumber = addr;
1236    }
1237
1238    private static void decodeIs91(BearerData bData)
1239        throws BitwiseInputStream.AccessException, CodingException
1240    {
1241        switch (bData.userData.msgType) {
1242        case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS:
1243            decodeIs91VoicemailStatus(bData);
1244            break;
1245        case UserData.IS91_MSG_TYPE_CLI:
1246            decodeIs91Cli(bData);
1247            break;
1248        case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL:
1249        case UserData.IS91_MSG_TYPE_SHORT_MESSAGE:
1250            decodeIs91ShortMessage(bData);
1251            break;
1252        default:
1253            throw new CodingException("unsupported IS-91 message type (" +
1254                    bData.userData.msgType + ")");
1255        }
1256    }
1257
1258    private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream)
1259        throws BitwiseInputStream.AccessException, CodingException
1260    {
1261        final int EXPECTED_PARAM_SIZE = 1 * 8;
1262        boolean decodeSuccess = false;
1263        int paramBits = inStream.read(8) * 8;
1264        if (paramBits >= EXPECTED_PARAM_SIZE) {
1265            paramBits -= EXPECTED_PARAM_SIZE;
1266            decodeSuccess = true;
1267            bData.userAckReq     = (inStream.read(1) == 1);
1268            bData.deliveryAckReq = (inStream.read(1) == 1);
1269            bData.readAckReq     = (inStream.read(1) == 1);
1270            bData.reportReq      = (inStream.read(1) == 1);
1271            inStream.skip(4);
1272        }
1273        if ((! decodeSuccess) || (paramBits > 0)) {
1274            Log.d(LOG_TAG, "REPLY_OPTION decode " +
1275                      (decodeSuccess ? "succeeded" : "failed") +
1276                      " (extra bits = " + paramBits + ")");
1277        }
1278        inStream.skip(paramBits);
1279        return decodeSuccess;
1280    }
1281
1282    private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream)
1283        throws BitwiseInputStream.AccessException, CodingException
1284    {
1285        final int EXPECTED_PARAM_SIZE = 1 * 8;
1286        boolean decodeSuccess = false;
1287        int paramBits = inStream.read(8) * 8;
1288        if (paramBits >= EXPECTED_PARAM_SIZE) {
1289            paramBits -= EXPECTED_PARAM_SIZE;
1290            decodeSuccess = true;
1291            bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
1292        }
1293        if ((! decodeSuccess) || (paramBits > 0)) {
1294            Log.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
1295                      (decodeSuccess ? "succeeded" : "failed") +
1296                      " (extra bits = " + paramBits + ")");
1297        }
1298        inStream.skip(paramBits);
1299        return decodeSuccess;
1300    }
1301
1302    private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)
1303        throws BitwiseInputStream.AccessException, CodingException
1304    {
1305        final int EXPECTED_PARAM_SIZE = 2 * 8;
1306        boolean decodeSuccess = false;
1307        int paramBits = inStream.read(8) * 8;
1308        if (paramBits >= EXPECTED_PARAM_SIZE) {
1309            paramBits -= EXPECTED_PARAM_SIZE;
1310            decodeSuccess = true;
1311            bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
1312        }
1313        if ((! decodeSuccess) || (paramBits > 0)) {
1314            Log.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
1315                      (decodeSuccess ? "succeeded" : "failed") +
1316                      " (extra bits = " + paramBits + ")");
1317        }
1318        inStream.skip(paramBits);
1319        return decodeSuccess;
1320    }
1321
1322    private static String decodeDtmfSmsAddress(byte[] rawData, int numFields)
1323        throws CodingException
1324    {
1325        /* DTMF 4-bit digit encoding, defined in at
1326         * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */
1327        StringBuffer strBuf = new StringBuffer(numFields);
1328        for (int i = 0; i < numFields; i++) {
1329            int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4)));
1330            if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10));
1331            else if (val == 10) strBuf.append('0');
1332            else if (val == 11) strBuf.append('*');
1333            else if (val == 12) strBuf.append('#');
1334            else throw new CodingException("invalid SMS address DTMF code (" + val + ")");
1335        }
1336        return strBuf.toString();
1337    }
1338
1339    private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException {
1340        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
1341            try {
1342                /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually
1343                 * just 7-bit ASCII encoding, with the MSB being zero. */
1344                addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII");
1345            } catch (java.io.UnsupportedEncodingException ex) {
1346                throw new CodingException("invalid SMS address ASCII code");
1347            }
1348        } else {
1349            addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits);
1350        }
1351    }
1352
1353    private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)
1354        throws BitwiseInputStream.AccessException, CodingException
1355    {
1356        int paramBits = inStream.read(8) * 8;
1357        CdmaSmsAddress addr = new CdmaSmsAddress();
1358        addr.digitMode = inStream.read(1);
1359        byte fieldBits = 4;
1360        byte consumedBits = 1;
1361        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
1362            addr.ton = inStream.read(3);
1363            addr.numberPlan = inStream.read(4);
1364            fieldBits = 8;
1365            consumedBits += 7;
1366        }
1367        addr.numberOfDigits = inStream.read(8);
1368        consumedBits += 8;
1369        int remainingBits = paramBits - consumedBits;
1370        int dataBits = addr.numberOfDigits * fieldBits;
1371        int paddingBits = remainingBits - dataBits;
1372        if (remainingBits < dataBits) {
1373            throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" +
1374                                      "remainingBits + " + remainingBits + ", dataBits + " +
1375                                      dataBits + ", paddingBits + " + paddingBits + ")");
1376        }
1377        addr.origBytes = inStream.readByteArray(dataBits);
1378        inStream.skip(paddingBits);
1379        decodeSmsAddress(addr);
1380        bData.callbackNumber = addr;
1381        return true;
1382    }
1383
1384    private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)
1385        throws BitwiseInputStream.AccessException, CodingException
1386    {
1387        final int EXPECTED_PARAM_SIZE = 1 * 8;
1388        boolean decodeSuccess = false;
1389        int paramBits = inStream.read(8) * 8;
1390        if (paramBits >= EXPECTED_PARAM_SIZE) {
1391            paramBits -= EXPECTED_PARAM_SIZE;
1392            decodeSuccess = true;
1393            bData.errorClass = inStream.read(2);
1394            bData.messageStatus = inStream.read(6);
1395        }
1396        if ((! decodeSuccess) || (paramBits > 0)) {
1397            Log.d(LOG_TAG, "MESSAGE_STATUS decode " +
1398                      (decodeSuccess ? "succeeded" : "failed") +
1399                      " (extra bits = " + paramBits + ")");
1400        }
1401        inStream.skip(paramBits);
1402        bData.messageStatusSet = decodeSuccess;
1403        return decodeSuccess;
1404    }
1405
1406    private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)
1407        throws BitwiseInputStream.AccessException, CodingException
1408    {
1409        final int EXPECTED_PARAM_SIZE = 6 * 8;
1410        boolean decodeSuccess = false;
1411        int paramBits = inStream.read(8) * 8;
1412        if (paramBits >= EXPECTED_PARAM_SIZE) {
1413            paramBits -= EXPECTED_PARAM_SIZE;
1414            decodeSuccess = true;
1415            bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
1416        }
1417        if ((! decodeSuccess) || (paramBits > 0)) {
1418            Log.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
1419                      (decodeSuccess ? "succeeded" : "failed") +
1420                      " (extra bits = " + paramBits + ")");
1421        }
1422        inStream.skip(paramBits);
1423        return decodeSuccess;
1424    }
1425
1426    private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)
1427        throws BitwiseInputStream.AccessException, CodingException
1428    {
1429        final int EXPECTED_PARAM_SIZE = 6 * 8;
1430        boolean decodeSuccess = false;
1431        int paramBits = inStream.read(8) * 8;
1432        if (paramBits >= EXPECTED_PARAM_SIZE) {
1433            paramBits -= EXPECTED_PARAM_SIZE;
1434            decodeSuccess = true;
1435            bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
1436        }
1437        if ((! decodeSuccess) || (paramBits > 0)) {
1438            Log.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
1439                      (decodeSuccess ? "succeeded" : "failed") +
1440                      " (extra bits = " + paramBits + ")");
1441        }
1442        inStream.skip(paramBits);
1443        return decodeSuccess;
1444    }
1445
1446    private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)
1447        throws BitwiseInputStream.AccessException, CodingException
1448    {
1449        final int EXPECTED_PARAM_SIZE = 6 * 8;
1450        boolean decodeSuccess = false;
1451        int paramBits = inStream.read(8) * 8;
1452        if (paramBits >= EXPECTED_PARAM_SIZE) {
1453            paramBits -= EXPECTED_PARAM_SIZE;
1454            decodeSuccess = true;
1455            bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
1456                    inStream.readByteArray(6 * 8));
1457        }
1458        if ((! decodeSuccess) || (paramBits > 0)) {
1459            Log.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
1460                      (decodeSuccess ? "succeeded" : "failed") +
1461                      " (extra bits = " + paramBits + ")");
1462        }
1463        inStream.skip(paramBits);
1464        return decodeSuccess;
1465    }
1466
1467    private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream)
1468        throws BitwiseInputStream.AccessException, CodingException
1469    {
1470        final int EXPECTED_PARAM_SIZE = 1 * 8;
1471        boolean decodeSuccess = false;
1472        int paramBits = inStream.read(8) * 8;
1473        if (paramBits >= EXPECTED_PARAM_SIZE) {
1474            paramBits -= EXPECTED_PARAM_SIZE;
1475            decodeSuccess = true;
1476            bData.deferredDeliveryTimeRelative = inStream.read(8);
1477        }
1478        if ((! decodeSuccess) || (paramBits > 0)) {
1479            Log.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
1480                      (decodeSuccess ? "succeeded" : "failed") +
1481                      " (extra bits = " + paramBits + ")");
1482        }
1483        inStream.skip(paramBits);
1484        bData.deferredDeliveryTimeRelativeSet = decodeSuccess;
1485        return decodeSuccess;
1486    }
1487
1488    private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)
1489        throws BitwiseInputStream.AccessException, CodingException
1490    {
1491        final int EXPECTED_PARAM_SIZE = 1 * 8;
1492        boolean decodeSuccess = false;
1493        int paramBits = inStream.read(8) * 8;
1494        if (paramBits >= EXPECTED_PARAM_SIZE) {
1495            paramBits -= EXPECTED_PARAM_SIZE;
1496            decodeSuccess = true;
1497            bData.validityPeriodRelative = inStream.read(8);
1498        }
1499        if ((! decodeSuccess) || (paramBits > 0)) {
1500            Log.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
1501                      (decodeSuccess ? "succeeded" : "failed") +
1502                      " (extra bits = " + paramBits + ")");
1503        }
1504        inStream.skip(paramBits);
1505        bData.validityPeriodRelativeSet = decodeSuccess;
1506        return decodeSuccess;
1507    }
1508
1509    private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)
1510        throws BitwiseInputStream.AccessException, CodingException
1511    {
1512        final int EXPECTED_PARAM_SIZE = 1 * 8;
1513        boolean decodeSuccess = false;
1514        int paramBits = inStream.read(8) * 8;
1515        if (paramBits >= EXPECTED_PARAM_SIZE) {
1516            paramBits -= EXPECTED_PARAM_SIZE;
1517            decodeSuccess = true;
1518            bData.privacy = inStream.read(2);
1519            inStream.skip(6);
1520        }
1521        if ((! decodeSuccess) || (paramBits > 0)) {
1522            Log.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
1523                      (decodeSuccess ? "succeeded" : "failed") +
1524                      " (extra bits = " + paramBits + ")");
1525        }
1526        inStream.skip(paramBits);
1527        bData.privacyIndicatorSet = decodeSuccess;
1528        return decodeSuccess;
1529    }
1530
1531    private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
1532        throws BitwiseInputStream.AccessException, CodingException
1533    {
1534        final int EXPECTED_PARAM_SIZE = 1 * 8;
1535        boolean decodeSuccess = false;
1536        int paramBits = inStream.read(8) * 8;
1537        if (paramBits >= EXPECTED_PARAM_SIZE) {
1538            paramBits -= EXPECTED_PARAM_SIZE;
1539            decodeSuccess = true;
1540            bData.language = inStream.read(8);
1541        }
1542        if ((! decodeSuccess) || (paramBits > 0)) {
1543            Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
1544                      (decodeSuccess ? "succeeded" : "failed") +
1545                      " (extra bits = " + paramBits + ")");
1546        }
1547        inStream.skip(paramBits);
1548        bData.languageIndicatorSet = decodeSuccess;
1549        return decodeSuccess;
1550    }
1551
1552    private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)
1553        throws BitwiseInputStream.AccessException, CodingException
1554    {
1555        final int EXPECTED_PARAM_SIZE = 1 * 8;
1556        boolean decodeSuccess = false;
1557        int paramBits = inStream.read(8) * 8;
1558        if (paramBits >= EXPECTED_PARAM_SIZE) {
1559            paramBits -= EXPECTED_PARAM_SIZE;
1560            decodeSuccess = true;
1561            bData.displayMode = inStream.read(2);
1562            inStream.skip(6);
1563        }
1564        if ((! decodeSuccess) || (paramBits > 0)) {
1565            Log.d(LOG_TAG, "DISPLAY_MODE decode " +
1566                      (decodeSuccess ? "succeeded" : "failed") +
1567                      " (extra bits = " + paramBits + ")");
1568        }
1569        inStream.skip(paramBits);
1570        bData.displayModeSet = decodeSuccess;
1571        return decodeSuccess;
1572    }
1573
1574    private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
1575        throws BitwiseInputStream.AccessException, CodingException
1576    {
1577        final int EXPECTED_PARAM_SIZE = 1 * 8;
1578        boolean decodeSuccess = false;
1579        int paramBits = inStream.read(8) * 8;
1580        if (paramBits >= EXPECTED_PARAM_SIZE) {
1581            paramBits -= EXPECTED_PARAM_SIZE;
1582            decodeSuccess = true;
1583            bData.priority = inStream.read(2);
1584            inStream.skip(6);
1585        }
1586        if ((! decodeSuccess) || (paramBits > 0)) {
1587            Log.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
1588                      (decodeSuccess ? "succeeded" : "failed") +
1589                      " (extra bits = " + paramBits + ")");
1590        }
1591        inStream.skip(paramBits);
1592        bData.priorityIndicatorSet = decodeSuccess;
1593        return decodeSuccess;
1594    }
1595
1596    private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)
1597        throws BitwiseInputStream.AccessException, CodingException
1598    {
1599        final int EXPECTED_PARAM_SIZE = 1 * 8;
1600        boolean decodeSuccess = false;
1601        int paramBits = inStream.read(8) * 8;
1602        if (paramBits >= EXPECTED_PARAM_SIZE) {
1603            paramBits -= EXPECTED_PARAM_SIZE;
1604            decodeSuccess = true;
1605            bData.alert = inStream.read(2);
1606            inStream.skip(6);
1607        }
1608        if ((! decodeSuccess) || (paramBits > 0)) {
1609            Log.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
1610                      (decodeSuccess ? "succeeded" : "failed") +
1611                      " (extra bits = " + paramBits + ")");
1612        }
1613        inStream.skip(paramBits);
1614        bData.alertIndicatorSet = decodeSuccess;
1615        return decodeSuccess;
1616    }
1617
1618    private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)
1619        throws BitwiseInputStream.AccessException, CodingException
1620    {
1621        final int EXPECTED_PARAM_SIZE = 1 * 8;
1622        boolean decodeSuccess = false;
1623        int paramBits = inStream.read(8) * 8;
1624        if (paramBits >= EXPECTED_PARAM_SIZE) {
1625            paramBits -= EXPECTED_PARAM_SIZE;
1626            decodeSuccess = true;
1627            bData.userResponseCode = inStream.read(8);
1628        }
1629        if ((! decodeSuccess) || (paramBits > 0)) {
1630            Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
1631                      (decodeSuccess ? "succeeded" : "failed") +
1632                      " (extra bits = " + paramBits + ")");
1633        }
1634        inStream.skip(paramBits);
1635        bData.userResponseCodeSet = decodeSuccess;
1636        return decodeSuccess;
1637    }
1638
1639    private static boolean decodeServiceCategoryProgramData(BearerData bData,
1640            BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
1641    {
1642        if (inStream.available() < 13) {
1643            throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
1644                    + inStream.available() + " bits available");
1645        }
1646
1647        int paramBits = inStream.read(8) * 8;
1648        int msgEncoding = inStream.read(5);
1649        paramBits -= 5;
1650
1651        if (inStream.available() < paramBits) {
1652            throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
1653                    + inStream.available() + " bits available (" + paramBits + " bits expected)");
1654        }
1655
1656        ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
1657
1658        final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
1659        boolean decodeSuccess = false;
1660        while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
1661            int operation = inStream.read(4);
1662            int category = (inStream.read(8) << 8) | inStream.read(8);
1663            int language = inStream.read(8);
1664            int maxMessages = inStream.read(8);
1665            int alertOption = inStream.read(4);
1666            int numFields = inStream.read(8);
1667            paramBits -= CATEGORY_FIELD_MIN_SIZE;
1668
1669            int textBits = getBitsForNumFields(msgEncoding, numFields);
1670            if (paramBits < textBits) {
1671                throw new CodingException("category name is " + textBits + " bits in length,"
1672                        + " but there are only " + paramBits + " bits available");
1673            }
1674
1675            UserData userData = new UserData();
1676            userData.msgEncoding = msgEncoding;
1677            userData.msgEncodingSet = true;
1678            userData.numFields = numFields;
1679            userData.payload = inStream.readByteArray(textBits);
1680            paramBits -= textBits;
1681
1682            decodeUserDataPayload(userData, false);
1683            String categoryName = userData.payloadStr;
1684            CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
1685                    language, maxMessages, alertOption, categoryName);
1686            programDataList.add(programData);
1687
1688            decodeSuccess = true;
1689        }
1690
1691        if ((! decodeSuccess) || (paramBits > 0)) {
1692            Log.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
1693                      (decodeSuccess ? "succeeded" : "failed") +
1694                      " (extra bits = " + paramBits + ')');
1695        }
1696
1697        inStream.skip(paramBits);
1698        bData.serviceCategoryProgramData = programDataList;
1699        return decodeSuccess;
1700    }
1701
1702    private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
1703        switch (serviceCategory) {
1704            case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
1705                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
1706
1707            case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
1708                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
1709
1710            case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
1711                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
1712
1713            case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
1714                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
1715
1716            case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
1717                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
1718
1719            default:
1720                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
1721        }
1722    }
1723
1724    /**
1725     * Calculates the number of bits to read for the specified number of encoded characters.
1726     * @param msgEncoding the message encoding to use
1727     * @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
1728     *  this is the number of bytes to read.
1729     * @return the number of bits to read from the stream
1730     * @throws CodingException if the specified encoding is not supported
1731     */
1732    private static int getBitsForNumFields(int msgEncoding, int numFields)
1733            throws CodingException {
1734        switch (msgEncoding) {
1735            case UserData.ENCODING_OCTET:
1736            case UserData.ENCODING_SHIFT_JIS:
1737            case UserData.ENCODING_KOREAN:
1738            case UserData.ENCODING_LATIN:
1739            case UserData.ENCODING_LATIN_HEBREW:
1740                return numFields * 8;
1741
1742            case UserData.ENCODING_IA5:
1743            case UserData.ENCODING_7BIT_ASCII:
1744            case UserData.ENCODING_GSM_7BIT_ALPHABET:
1745                return numFields * 7;
1746
1747            case UserData.ENCODING_UNICODE_16:
1748                return numFields * 16;
1749
1750            default:
1751                throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
1752        }
1753    }
1754
1755    /**
1756     * CMAS message decoding.
1757     * (See TIA-1149-0-1, CMAS over CDMA)
1758     *
1759     * @param serviceCategory is the service category from the SMS envelope
1760     */
1761    private static void decodeCmasUserData(BearerData bData, int serviceCategory)
1762            throws BitwiseInputStream.AccessException, CodingException {
1763        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
1764        if (inStream.available() < 8) {
1765            throw new CodingException("emergency CB with no CMAE_protocol_version");
1766        }
1767        int protocolVersion = inStream.read(8);
1768        if (protocolVersion != 0) {
1769            throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
1770        }
1771
1772        int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
1773        int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
1774        int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
1775        int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
1776        int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
1777        int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
1778
1779        while (inStream.available() >= 16) {
1780            int recordType = inStream.read(8);
1781            int recordLen = inStream.read(8);
1782            switch (recordType) {
1783                case 0:     // Type 0 elements (Alert text)
1784                    UserData alertUserData = new UserData();
1785                    alertUserData.msgEncoding = inStream.read(5);
1786                    alertUserData.msgEncodingSet = true;
1787                    alertUserData.msgType = 0;
1788
1789                    int numFields;                          // number of chars to decode
1790                    switch (alertUserData.msgEncoding) {
1791                        case UserData.ENCODING_OCTET:
1792                        case UserData.ENCODING_LATIN:
1793                            numFields = recordLen - 1;      // subtract 1 byte for encoding
1794                            break;
1795
1796                        case UserData.ENCODING_IA5:
1797                        case UserData.ENCODING_7BIT_ASCII:
1798                        case UserData.ENCODING_GSM_7BIT_ALPHABET:
1799                            numFields = ((recordLen * 8) - 5) / 7;  // subtract 5 bits for encoding
1800                            break;
1801
1802                        case UserData.ENCODING_UNICODE_16:
1803                            numFields = (recordLen - 1) / 2;
1804                            break;
1805
1806                        default:
1807                            numFields = 0;      // unsupported encoding
1808                    }
1809
1810                    alertUserData.numFields = numFields;
1811                    alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
1812                    decodeUserDataPayload(alertUserData, false);
1813                    bData.userData = alertUserData;
1814                    break;
1815
1816                case 1:     // Type 1 elements
1817                    category = inStream.read(8);
1818                    responseType = inStream.read(8);
1819                    severity = inStream.read(4);
1820                    urgency = inStream.read(4);
1821                    certainty = inStream.read(4);
1822                    inStream.skip(recordLen * 8 - 28);
1823                    break;
1824
1825                default:
1826                    Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
1827                    inStream.skip(recordLen * 8);
1828                    break;
1829            }
1830        }
1831
1832        bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
1833                urgency, certainty);
1834    }
1835
1836    /**
1837     * Create BearerData object from serialized representation.
1838     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
1839     *
1840     * @param smsData byte array of raw encoded SMS bearer data.
1841     * @return an instance of BearerData.
1842     */
1843    public static BearerData decode(byte[] smsData) {
1844        return decode(smsData, 0);
1845    }
1846
1847    private static boolean isCmasAlertCategory(int category) {
1848        return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
1849                && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
1850    }
1851
1852    /**
1853     * Create BearerData object from serialized representation.
1854     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
1855     *
1856     * @param smsData byte array of raw encoded SMS bearer data.
1857     * @param serviceCategory the envelope service category (for CMAS alert handling)
1858     * @return an instance of BearerData.
1859     */
1860    public static BearerData decode(byte[] smsData, int serviceCategory) {
1861        try {
1862            BitwiseInputStream inStream = new BitwiseInputStream(smsData);
1863            BearerData bData = new BearerData();
1864            int foundSubparamMask = 0;
1865            while (inStream.available() > 0) {
1866                int subparamId = inStream.read(8);
1867                int subparamIdBit = 1 << subparamId;
1868                if ((foundSubparamMask & subparamIdBit) != 0) {
1869                    throw new CodingException("illegal duplicate subparameter (" +
1870                                              subparamId + ")");
1871                }
1872                boolean decodeSuccess;
1873                switch (subparamId) {
1874                case SUBPARAM_MESSAGE_IDENTIFIER:
1875                    decodeSuccess = decodeMessageId(bData, inStream);
1876                    break;
1877                case SUBPARAM_USER_DATA:
1878                    decodeSuccess = decodeUserData(bData, inStream);
1879                    break;
1880                case SUBPARAM_USER_RESPONSE_CODE:
1881                    decodeSuccess = decodeUserResponseCode(bData, inStream);
1882                    break;
1883                case SUBPARAM_REPLY_OPTION:
1884                    decodeSuccess = decodeReplyOption(bData, inStream);
1885                    break;
1886                case SUBPARAM_NUMBER_OF_MESSAGES:
1887                    decodeSuccess = decodeMsgCount(bData, inStream);
1888                    break;
1889                case SUBPARAM_CALLBACK_NUMBER:
1890                    decodeSuccess = decodeCallbackNumber(bData, inStream);
1891                    break;
1892                case SUBPARAM_MESSAGE_STATUS:
1893                    decodeSuccess = decodeMsgStatus(bData, inStream);
1894                    break;
1895                case SUBPARAM_MESSAGE_CENTER_TIME_STAMP:
1896                    decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream);
1897                    break;
1898                case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE:
1899                    decodeSuccess = decodeValidityAbs(bData, inStream);
1900                    break;
1901                case SUBPARAM_VALIDITY_PERIOD_RELATIVE:
1902                    decodeSuccess = decodeValidityRel(bData, inStream);
1903                    break;
1904                case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE:
1905                    decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream);
1906                    break;
1907                case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE:
1908                    decodeSuccess = decodeDeferredDeliveryRel(bData, inStream);
1909                    break;
1910                case SUBPARAM_PRIVACY_INDICATOR:
1911                    decodeSuccess = decodePrivacyIndicator(bData, inStream);
1912                    break;
1913                case SUBPARAM_LANGUAGE_INDICATOR:
1914                    decodeSuccess = decodeLanguageIndicator(bData, inStream);
1915                    break;
1916                case SUBPARAM_MESSAGE_DISPLAY_MODE:
1917                    decodeSuccess = decodeDisplayMode(bData, inStream);
1918                    break;
1919                case SUBPARAM_PRIORITY_INDICATOR:
1920                    decodeSuccess = decodePriorityIndicator(bData, inStream);
1921                    break;
1922                case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY:
1923                    decodeSuccess = decodeMsgDeliveryAlert(bData, inStream);
1924                    break;
1925                case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
1926                    decodeSuccess = decodeDepositIndex(bData, inStream);
1927                    break;
1928                case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
1929                    decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
1930                    break;
1931                default:
1932                    throw new CodingException("unsupported bearer data subparameter ("
1933                                              + subparamId + ")");
1934                }
1935                if (decodeSuccess) foundSubparamMask |= subparamIdBit;
1936            }
1937            if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
1938                throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
1939            }
1940            if (bData.userData != null) {
1941                if (isCmasAlertCategory(serviceCategory)) {
1942                    decodeCmasUserData(bData, serviceCategory);
1943                } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
1944                    if ((foundSubparamMask ^
1945                             (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
1946                             (1 << SUBPARAM_USER_DATA))
1947                            != 0) {
1948                        Log.e(LOG_TAG, "IS-91 must occur without extra subparams (" +
1949                              foundSubparamMask + ")");
1950                    }
1951                    decodeIs91(bData);
1952                } else {
1953                    decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
1954                }
1955            }
1956            return bData;
1957        } catch (BitwiseInputStream.AccessException ex) {
1958            Log.e(LOG_TAG, "BearerData decode failed: " + ex);
1959        } catch (CodingException ex) {
1960            Log.e(LOG_TAG, "BearerData decode failed: " + ex);
1961        }
1962        return null;
1963    }
1964}
1965