Initial move to CellBroadcastService dir

Bug: 135956699
Test: manually verified that framework still binds to CB service
Change-Id: I34b4e8070d56c69d1ac799159bba085e387d768a
diff --git a/src/com/android/cellbroadcastservice/BearerData.java b/src/com/android/cellbroadcastservice/BearerData.java
new file mode 100644
index 0000000..584cd15
--- /dev/null
+++ b/src/com/android/cellbroadcastservice/BearerData.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cellbroadcastservice;
+
+import android.content.res.Resources;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.util.BitwiseInputStream;
+
+/**
+ * An object to decode CDMA SMS bearer data.
+ */
+public final class BearerData {
+    private final static String LOG_TAG = "BearerData";
+
+    /**
+     * Bearer Data Subparameter Identifiers
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
+     * NOTE: Unneeded subparameter types are not included
+     */
+    private final static byte SUBPARAM_MESSAGE_IDENTIFIER               = 0x00;
+    private final static byte SUBPARAM_USER_DATA                        = 0x01;
+    private final static byte SUBPARAM_PRIORITY_INDICATOR               = 0x08;
+    private final static byte SUBPARAM_LANGUAGE_INDICATOR               = 0x0D;
+
+    // All other values after this are reserved.
+    private final static byte SUBPARAM_ID_LAST_DEFINED                  = 0x17;
+
+    /**
+     * Supported priority modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+     */
+    public static final int PRIORITY_NORMAL        = 0x0;
+    public static final int PRIORITY_INTERACTIVE   = 0x1;
+    public static final int PRIORITY_URGENT        = 0x2;
+    public static final int PRIORITY_EMERGENCY     = 0x3;
+
+    /**
+     * Language Indicator values.  NOTE: the spec (3GPP2 C.S0015-B,
+     * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
+     * refers to C.R1001-D but that reference has been crossed out.
+     * It would seem reasonable to assume the values from C.R1001-F
+     * (table 9.2-1) are to be used instead.
+     */
+    public static final int LANGUAGE_UNKNOWN  = 0x00;
+    public static final int LANGUAGE_ENGLISH  = 0x01;
+    public static final int LANGUAGE_FRENCH   = 0x02;
+    public static final int LANGUAGE_SPANISH  = 0x03;
+    public static final int LANGUAGE_JAPANESE = 0x04;
+    public static final int LANGUAGE_KOREAN   = 0x05;
+    public static final int LANGUAGE_CHINESE  = 0x06;
+    public static final int LANGUAGE_HEBREW   = 0x07;
+
+    /**
+     * 16-bit value indicating the message ID, which increments modulo 65536.
+     * (Special rules apply for WAP-messages.)
+     * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+     */
+    public int messageId;
+
+    /**
+     * Priority modes for CDMA SMS message (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+     */
+    public int priority = PRIORITY_NORMAL;
+
+    /**
+     * Language indicator for CDMA SMS message.
+     */
+    public int language = LANGUAGE_UNKNOWN;
+
+    /**
+     * 1-bit value that indicates whether a User Data Header (UDH) is present.
+     * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+     *
+     * NOTE: during encoding, this value will be set based on the
+     * presence of a UDH in the structured data, any existing setting
+     * will be overwritten.
+     */
+    public boolean hasUserDataHeader;
+
+    /**
+     * Information on the user data
+     * (e.g. padding bits, user data, user data header, etc)
+     * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
+     */
+    public UserData userData;
+
+    /**
+     * CMAS warning notification information.
+     * @see #decodeCmasUserData(BearerData, int)
+     */
+    public SmsCbCmasInfo cmasWarningInfo;
+
+    /**
+     * Construct an empty BearerData.
+     */
+    private BearerData() {}
+
+    private static class CodingException extends Exception {
+        public CodingException(String s) {
+            super(s);
+        }
+    }
+
+    /**
+     * Returns the language indicator as a two-character ISO 639 string.
+     * @return a two character ISO 639 language code
+     */
+    public String getLanguage() {
+        return getLanguageCodeForValue(language);
+    }
+
+    /**
+     * Converts a CDMA language indicator value to an ISO 639 two character language code.
+     * @param languageValue the CDMA language value to convert
+     * @return the two character ISO 639 language code for the specified value, or null if unknown
+     */
+    private static String getLanguageCodeForValue(int languageValue) {
+        switch (languageValue) {
+            case LANGUAGE_ENGLISH:
+                return "en";
+
+            case LANGUAGE_FRENCH:
+                return "fr";
+
+            case LANGUAGE_SPANISH:
+                return "es";
+
+            case LANGUAGE_JAPANESE:
+                return "ja";
+
+            case LANGUAGE_KOREAN:
+                return "ko";
+
+            case LANGUAGE_CHINESE:
+                return "zh";
+
+            case LANGUAGE_HEBREW:
+                return "he";
+
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("BearerData ");
+        builder.append(", messageId=" + messageId);
+        builder.append(", hasUserDataHeader=" + hasUserDataHeader);
+        builder.append(", userData=" + userData);
+        builder.append(" }");
+        return builder.toString();
+    }
+
+    private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
+            throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 3 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            inStream.skip(4); // skip messageType
+            bData.messageId = inStream.read(8) << 8;
+            bData.messageId |= inStream.read(8);
+            bData.hasUserDataHeader = (inStream.read(1) == 1);
+            inStream.skip(3);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
+                    (decodeSuccess ? "succeeded" : "failed") +
+                    " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeReserved(BitwiseInputStream inStream, int subparamId)
+            throws BitwiseInputStream.AccessException, CodingException
+    {
+        boolean decodeSuccess = false;
+        int subparamLen = inStream.read(8); // SUBPARAM_LEN
+        int paramBits = subparamLen * 8;
+        if (paramBits <= inStream.available()) {
+            decodeSuccess = true;
+            inStream.skip(paramBits);
+        }
+        Log.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
+                + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
+        if (!decodeSuccess) {
+            throw new CodingException("RESERVED bearer data subparameter " + subparamId
+                    + " had invalid SUBPARAM_LEN " + subparamLen);
+        }
+
+        return decodeSuccess;
+    }
+
+    private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
+            throws BitwiseInputStream.AccessException
+    {
+        int paramBits = inStream.read(8) * 8;
+        bData.userData = new UserData();
+        bData.userData.msgEncoding = inStream.read(5);
+        bData.userData.msgEncodingSet = true;
+        bData.userData.msgType = 0;
+        int consumedBits = 5;
+        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+                (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+            bData.userData.msgType = inStream.read(8);
+            consumedBits += 8;
+        }
+        bData.userData.numFields = inStream.read(8);
+        consumedBits += 8;
+        int dataBits = paramBits - consumedBits;
+        bData.userData.payload = inStream.readByteArray(dataBits);
+        return true;
+    }
+
+    private static String decodeUtf8(byte[] data, int offset, int numFields)
+            throws CodingException
+    {
+        return decodeCharset(data, offset, numFields, 1, "UTF-8");
+    }
+
+    private static String decodeUtf16(byte[] data, int offset, int numFields)
+            throws CodingException
+    {
+        // Subtract header and possible padding byte (at end) from num fields.
+        int padding = offset % 2;
+        numFields -= (offset + padding) / 2;
+        return decodeCharset(data, offset, numFields, 2, "utf-16be");
+    }
+
+    private static String decodeCharset(byte[] data, int offset, int numFields, int width,
+            String charset) throws CodingException
+    {
+        if (numFields < 0 || (numFields * width + offset) > data.length) {
+            // Try to decode the max number of characters in payload
+            int padding = offset % width;
+            int maxNumFields = (data.length - offset - padding) / width;
+            if (maxNumFields < 0) {
+                throw new CodingException(charset + " decode failed: offset out of range");
+            }
+            Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
+                    + numFields + " data.length = " + data.length + " maxNumFields = "
+                    + maxNumFields);
+            numFields = maxNumFields;
+        }
+        try {
+            return new String(data, offset, numFields * width, charset);
+        } catch (java.io.UnsupportedEncodingException ex) {
+            throw new CodingException(charset + " decode failed: " + ex);
+        }
+    }
+
+    private static String decode7bitAscii(byte[] data, int offset, int numFields)
+            throws CodingException
+    {
+        try {
+            int offsetBits = offset * 8;
+            int offsetSeptets = (offsetBits + 6) / 7;
+            numFields -= offsetSeptets;
+
+            StringBuffer strBuf = new StringBuffer(numFields);
+            BitwiseInputStream inStream = new BitwiseInputStream(data);
+            int wantedBits = (offsetSeptets * 7) + (numFields * 7);
+            if (inStream.available() < wantedBits) {
+                throw new CodingException("insufficient data (wanted " + wantedBits +
+                        " bits, but only have " + inStream.available() + ")");
+            }
+            inStream.skip(offsetSeptets * 7);
+            for (int i = 0; i < numFields; i++) {
+                int charCode = inStream.read(7);
+                if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
+                        (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
+                    strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
+                } else if (charCode == UserData.ASCII_NL_INDEX) {
+                    strBuf.append('\n');
+                } else if (charCode == UserData.ASCII_CR_INDEX) {
+                    strBuf.append('\r');
+                } else {
+                    /* For other charCodes, they are unprintable, and so simply use SPACE. */
+                    strBuf.append(' ');
+                }
+            }
+            return strBuf.toString();
+        } catch (BitwiseInputStream.AccessException ex) {
+            throw new CodingException("7bit ASCII decode failed: " + ex);
+        }
+    }
+
+    private static String decode7bitGsm(byte[] data, int offset, int numFields)
+            throws CodingException
+    {
+        // Start reading from the next 7-bit aligned boundary after offset.
+        int offsetBits = offset * 8;
+        int offsetSeptets = (offsetBits + 6) / 7;
+        numFields -= offsetSeptets;
+        int paddingBits = (offsetSeptets * 7) - offsetBits;
+        String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
+                0, 0);
+        if (result == null) {
+            throw new CodingException("7bit GSM decoding failed");
+        }
+        return result;
+    }
+
+    private static String decodeLatin(byte[] data, int offset, int numFields)
+            throws CodingException
+    {
+        return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
+    }
+
+    private static String decodeShiftJis(byte[] data, int offset, int numFields)
+            throws CodingException
+    {
+        return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
+    }
+
+    private static String decodeGsmDcs(byte[] data, int offset, int numFields, int msgType)
+            throws CodingException
+    {
+        if ((msgType & 0xC0) != 0) {
+            throw new CodingException("unsupported coding group ("
+                    + msgType + ")");
+        }
+
+        switch ((msgType >> 2) & 0x3) {
+            case UserData.ENCODING_GSM_DCS_7BIT:
+                return decode7bitGsm(data, offset, numFields);
+            case UserData.ENCODING_GSM_DCS_8BIT:
+                return decodeUtf8(data, offset, numFields);
+            case UserData.ENCODING_GSM_DCS_16BIT:
+                return decodeUtf16(data, offset, numFields);
+            default:
+                throw new CodingException("unsupported user msgType encoding ("
+                        + msgType + ")");
+        }
+    }
+
+    private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
+            throws CodingException
+    {
+        int offset = 0;
+        if (hasUserDataHeader) {
+            int udhLen = userData.payload[0] & 0x00FF;
+            offset += udhLen + 1;
+            byte[] headerData = new byte[udhLen];
+            System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
+            userData.userDataHeader = SmsHeader.fromByteArray(headerData);
+        }
+        switch (userData.msgEncoding) {
+            case UserData.ENCODING_OCTET:
+                /*
+                 *  Octet decoding depends on the carrier service.
+                 */
+                boolean decodingtypeUTF8 = Resources.getSystem()
+                        .getBoolean(R.bool.config_sms_utf8_support);
+
+                // Strip off any padding bytes, meaning any differences between the length of the
+                // array and the target length specified by numFields.  This is to avoid any
+                // confusion by code elsewhere that only considers the payload array length.
+                byte[] payload = new byte[userData.numFields];
+                int copyLen = userData.numFields < userData.payload.length
+                        ? userData.numFields : userData.payload.length;
+
+                System.arraycopy(userData.payload, 0, payload, 0, copyLen);
+                userData.payload = payload;
+
+                if (!decodingtypeUTF8) {
+                    // There are many devices in the market that send 8bit text sms (latin encoded) as
+                    // octet encoded.
+                    userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+                } else {
+                    userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
+                }
+                break;
+
+            case UserData.ENCODING_IA5:
+            case UserData.ENCODING_7BIT_ASCII:
+                userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
+                break;
+            case UserData.ENCODING_UNICODE_16:
+                userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
+                break;
+            case UserData.ENCODING_GSM_7BIT_ALPHABET:
+                userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
+                break;
+            case UserData.ENCODING_LATIN:
+                userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+                break;
+            case UserData.ENCODING_SHIFT_JIS:
+                userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
+                break;
+            case UserData.ENCODING_GSM_DCS:
+                userData.payloadStr = decodeGsmDcs(userData.payload, offset,
+                        userData.numFields, userData.msgType);
+                break;
+            default:
+                throw new CodingException("unsupported user data encoding ("
+                        + userData.msgEncoding + ")");
+        }
+    }
+
+    private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
+            throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.language = inStream.read(8);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
+                    (decodeSuccess ? "succeeded" : "failed") +
+                    " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
+            throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.priority = inStream.read(2);
+            inStream.skip(6);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Log.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
+                    (decodeSuccess ? "succeeded" : "failed") +
+                    " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
+        switch (serviceCategory) {
+            case CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
+                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+            case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT:
+                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+            case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT:
+                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+            case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+            case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+            default:
+                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+        }
+    }
+
+    /**
+     * CMAS message decoding.
+     * (See TIA-1149-0-1, CMAS over CDMA)
+     *
+     * @param serviceCategory is the service category from the SMS envelope
+     */
+    private static void decodeCmasUserData(BearerData bData, int serviceCategory)
+            throws BitwiseInputStream.AccessException, CodingException {
+        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+        if (inStream.available() < 8) {
+            throw new CodingException("emergency CB with no CMAE_protocol_version");
+        }
+        int protocolVersion = inStream.read(8);
+        if (protocolVersion != 0) {
+            throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
+        }
+
+        int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
+        int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+        int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+        int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+        int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+        int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+
+        while (inStream.available() >= 16) {
+            int recordType = inStream.read(8);
+            int recordLen = inStream.read(8);
+            switch (recordType) {
+                case 0:     // Type 0 elements (Alert text)
+                    UserData alertUserData = new UserData();
+                    alertUserData.msgEncoding = inStream.read(5);
+                    alertUserData.msgEncodingSet = true;
+                    alertUserData.msgType = 0;
+
+                    int numFields;                          // number of chars to decode
+                    switch (alertUserData.msgEncoding) {
+                        case UserData.ENCODING_OCTET:
+                        case UserData.ENCODING_LATIN:
+                            numFields = recordLen - 1;      // subtract 1 byte for encoding
+                            break;
+
+                        case UserData.ENCODING_IA5:
+                        case UserData.ENCODING_7BIT_ASCII:
+                        case UserData.ENCODING_GSM_7BIT_ALPHABET:
+                            numFields = ((recordLen * 8) - 5) / 7;  // subtract 5 bits for encoding
+                            break;
+
+                        case UserData.ENCODING_UNICODE_16:
+                            numFields = (recordLen - 1) / 2;
+                            break;
+
+                        default:
+                            numFields = 0;      // unsupported encoding
+                    }
+
+                    alertUserData.numFields = numFields;
+                    alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
+                    decodeUserDataPayload(alertUserData, false);
+                    bData.userData = alertUserData;
+                    break;
+
+                case 1:     // Type 1 elements
+                    category = inStream.read(8);
+                    responseType = inStream.read(8);
+                    severity = inStream.read(4);
+                    urgency = inStream.read(4);
+                    certainty = inStream.read(4);
+                    inStream.skip(recordLen * 8 - 28);
+                    break;
+
+                default:
+                    Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
+                    inStream.skip(recordLen * 8);
+                    break;
+            }
+        }
+
+        bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
+                urgency, certainty);
+    }
+
+    private static boolean isCmasAlertCategory(int category) {
+        return category >= CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
+                && category <= CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;
+    }
+
+    /**
+     * Create BearerData object from serialized representation.
+     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+     *
+     * @param smsData byte array of raw encoded SMS bearer data.
+     * @param serviceCategory the envelope service category (for CMAS alert handling)
+     * @return an instance of BearerData.
+     */
+    public static BearerData decode(byte[] smsData, int serviceCategory) {
+        try {
+            BitwiseInputStream inStream = new BitwiseInputStream(smsData);
+            BearerData bData = new BearerData();
+            int foundSubparamMask = 0;
+            while (inStream.available() > 0) {
+                int subparamId = inStream.read(8);
+                int subparamIdBit = 1 << subparamId;
+                // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
+                // as 32th bit is the max bit in int.
+                // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
+                // last defined subparam ID is 23 (00010111 = 0x17 = 23).
+                // Only do duplicate subparam ID check if subparam is within defined value as
+                // reserved subparams are just skipped.
+                if ((foundSubparamMask & subparamIdBit) != 0 &&
+                        (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+                        subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+                    throw new CodingException("illegal duplicate subparameter (" +
+                                              subparamId + ")");
+                }
+                boolean decodeSuccess;
+                switch (subparamId) {
+                case SUBPARAM_MESSAGE_IDENTIFIER:
+                    decodeSuccess = decodeMessageId(bData, inStream);
+                    break;
+                case SUBPARAM_USER_DATA:
+                    decodeSuccess = decodeUserData(bData, inStream);
+                    break;
+                case SUBPARAM_LANGUAGE_INDICATOR:
+                    decodeSuccess = decodeLanguageIndicator(bData, inStream);
+                    break;
+                case SUBPARAM_PRIORITY_INDICATOR:
+                    decodeSuccess = decodePriorityIndicator(bData, inStream);
+                    break;
+                default:
+                    decodeSuccess = decodeReserved(inStream, subparamId);
+                }
+                if (decodeSuccess &&
+                        (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+                        subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+                    foundSubparamMask |= subparamIdBit;
+                }
+            }
+            if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
+                throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+            }
+            if (bData.userData != null) {
+                if (isCmasAlertCategory(serviceCategory)) {
+                    decodeCmasUserData(bData, serviceCategory);
+                } else {
+                    decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
+                }
+            }
+            return bData;
+        } catch (BitwiseInputStream.AccessException ex) {
+            Log.e(LOG_TAG, "BearerData decode failed: " + ex);
+        } catch (CodingException ex) {
+            Log.e(LOG_TAG, "BearerData decode failed: " + ex);
+        }
+        return null;
+    }
+}