| /* |
| * 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.Context; |
| import android.telephony.SmsCbCmasInfo; |
| import android.telephony.cdma.CdmaSmsCbProgramData; |
| import android.util.Log; |
| |
| /** |
| * 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 static final byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00; |
| private static final byte SUBPARAM_USER_DATA = 0x01; |
| private static final byte SUBPARAM_PRIORITY_INDICATOR = 0x08; |
| private static final byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D; |
| |
| // All other values after this are reserved. |
| private static final 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; |
| |
| /** |
| * Supported message types for CDMA SMS messages |
| * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1) |
| * Used for CdmaSmsCbTest. |
| */ |
| public static final int MESSAGE_TYPE_DELIVER = 0x01; |
| |
| /** |
| * 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(Context context, 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 = context.getResources() |
| .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(Context context, 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(context, 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(Context context, byte[] smsData, int serviceCategory) |
| throws CodingException, BitwiseInputStream.AccessException { |
| 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(context, bData, serviceCategory); |
| } else { |
| decodeUserDataPayload(context, bData.userData, bData.hasUserDataHeader); |
| } |
| } |
| return bData; |
| } |
| } |