blob: 584cd15392a136307ac1ac783d2dd9cb3d4c0bb8 [file] [log] [blame]
Jordan Liudfcbfaf2019-10-11 11:42:03 -07001/*
2 * Copyright (C) 2019 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.cellbroadcastservice;
18
19import android.content.res.Resources;
20import android.telephony.SmsCbCmasInfo;
21import android.telephony.cdma.CdmaSmsCbProgramData;
22import android.util.Log;
23
24import com.android.internal.telephony.GsmAlphabet;
25import com.android.internal.telephony.SmsHeader;
26import com.android.internal.util.BitwiseInputStream;
27
28/**
29 * An object to decode CDMA SMS bearer data.
30 */
31public final class BearerData {
32 private final static String LOG_TAG = "BearerData";
33
34 /**
35 * Bearer Data Subparameter Identifiers
36 * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
37 * NOTE: Unneeded subparameter types are not included
38 */
39 private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00;
40 private final static byte SUBPARAM_USER_DATA = 0x01;
41 private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08;
42 private final static byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D;
43
44 // All other values after this are reserved.
45 private final static byte SUBPARAM_ID_LAST_DEFINED = 0x17;
46
47 /**
48 * Supported priority modes for CDMA SMS messages
49 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
50 */
51 public static final int PRIORITY_NORMAL = 0x0;
52 public static final int PRIORITY_INTERACTIVE = 0x1;
53 public static final int PRIORITY_URGENT = 0x2;
54 public static final int PRIORITY_EMERGENCY = 0x3;
55
56 /**
57 * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B,
58 * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
59 * refers to C.R1001-D but that reference has been crossed out.
60 * It would seem reasonable to assume the values from C.R1001-F
61 * (table 9.2-1) are to be used instead.
62 */
63 public static final int LANGUAGE_UNKNOWN = 0x00;
64 public static final int LANGUAGE_ENGLISH = 0x01;
65 public static final int LANGUAGE_FRENCH = 0x02;
66 public static final int LANGUAGE_SPANISH = 0x03;
67 public static final int LANGUAGE_JAPANESE = 0x04;
68 public static final int LANGUAGE_KOREAN = 0x05;
69 public static final int LANGUAGE_CHINESE = 0x06;
70 public static final int LANGUAGE_HEBREW = 0x07;
71
72 /**
73 * 16-bit value indicating the message ID, which increments modulo 65536.
74 * (Special rules apply for WAP-messages.)
75 * (See 3GPP2 C.S0015-B, v2, 4.5.1)
76 */
77 public int messageId;
78
79 /**
80 * Priority modes for CDMA SMS message (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
81 */
82 public int priority = PRIORITY_NORMAL;
83
84 /**
85 * Language indicator for CDMA SMS message.
86 */
87 public int language = LANGUAGE_UNKNOWN;
88
89 /**
90 * 1-bit value that indicates whether a User Data Header (UDH) is present.
91 * (See 3GPP2 C.S0015-B, v2, 4.5.1)
92 *
93 * NOTE: during encoding, this value will be set based on the
94 * presence of a UDH in the structured data, any existing setting
95 * will be overwritten.
96 */
97 public boolean hasUserDataHeader;
98
99 /**
100 * Information on the user data
101 * (e.g. padding bits, user data, user data header, etc)
102 * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
103 */
104 public UserData userData;
105
106 /**
107 * CMAS warning notification information.
108 * @see #decodeCmasUserData(BearerData, int)
109 */
110 public SmsCbCmasInfo cmasWarningInfo;
111
112 /**
113 * Construct an empty BearerData.
114 */
115 private BearerData() {}
116
117 private static class CodingException extends Exception {
118 public CodingException(String s) {
119 super(s);
120 }
121 }
122
123 /**
124 * Returns the language indicator as a two-character ISO 639 string.
125 * @return a two character ISO 639 language code
126 */
127 public String getLanguage() {
128 return getLanguageCodeForValue(language);
129 }
130
131 /**
132 * Converts a CDMA language indicator value to an ISO 639 two character language code.
133 * @param languageValue the CDMA language value to convert
134 * @return the two character ISO 639 language code for the specified value, or null if unknown
135 */
136 private static String getLanguageCodeForValue(int languageValue) {
137 switch (languageValue) {
138 case LANGUAGE_ENGLISH:
139 return "en";
140
141 case LANGUAGE_FRENCH:
142 return "fr";
143
144 case LANGUAGE_SPANISH:
145 return "es";
146
147 case LANGUAGE_JAPANESE:
148 return "ja";
149
150 case LANGUAGE_KOREAN:
151 return "ko";
152
153 case LANGUAGE_CHINESE:
154 return "zh";
155
156 case LANGUAGE_HEBREW:
157 return "he";
158
159 default:
160 return null;
161 }
162 }
163
164 @Override
165 public String toString() {
166 StringBuilder builder = new StringBuilder();
167 builder.append("BearerData ");
168 builder.append(", messageId=" + messageId);
169 builder.append(", hasUserDataHeader=" + hasUserDataHeader);
170 builder.append(", userData=" + userData);
171 builder.append(" }");
172 return builder.toString();
173 }
174
175 private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
176 throws BitwiseInputStream.AccessException {
177 final int EXPECTED_PARAM_SIZE = 3 * 8;
178 boolean decodeSuccess = false;
179 int paramBits = inStream.read(8) * 8;
180 if (paramBits >= EXPECTED_PARAM_SIZE) {
181 paramBits -= EXPECTED_PARAM_SIZE;
182 decodeSuccess = true;
183 inStream.skip(4); // skip messageType
184 bData.messageId = inStream.read(8) << 8;
185 bData.messageId |= inStream.read(8);
186 bData.hasUserDataHeader = (inStream.read(1) == 1);
187 inStream.skip(3);
188 }
189 if ((! decodeSuccess) || (paramBits > 0)) {
190 Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
191 (decodeSuccess ? "succeeded" : "failed") +
192 " (extra bits = " + paramBits + ")");
193 }
194 inStream.skip(paramBits);
195 return decodeSuccess;
196 }
197
198 private static boolean decodeReserved(BitwiseInputStream inStream, int subparamId)
199 throws BitwiseInputStream.AccessException, CodingException
200 {
201 boolean decodeSuccess = false;
202 int subparamLen = inStream.read(8); // SUBPARAM_LEN
203 int paramBits = subparamLen * 8;
204 if (paramBits <= inStream.available()) {
205 decodeSuccess = true;
206 inStream.skip(paramBits);
207 }
208 Log.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
209 + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
210 if (!decodeSuccess) {
211 throw new CodingException("RESERVED bearer data subparameter " + subparamId
212 + " had invalid SUBPARAM_LEN " + subparamLen);
213 }
214
215 return decodeSuccess;
216 }
217
218 private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
219 throws BitwiseInputStream.AccessException
220 {
221 int paramBits = inStream.read(8) * 8;
222 bData.userData = new UserData();
223 bData.userData.msgEncoding = inStream.read(5);
224 bData.userData.msgEncodingSet = true;
225 bData.userData.msgType = 0;
226 int consumedBits = 5;
227 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
228 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
229 bData.userData.msgType = inStream.read(8);
230 consumedBits += 8;
231 }
232 bData.userData.numFields = inStream.read(8);
233 consumedBits += 8;
234 int dataBits = paramBits - consumedBits;
235 bData.userData.payload = inStream.readByteArray(dataBits);
236 return true;
237 }
238
239 private static String decodeUtf8(byte[] data, int offset, int numFields)
240 throws CodingException
241 {
242 return decodeCharset(data, offset, numFields, 1, "UTF-8");
243 }
244
245 private static String decodeUtf16(byte[] data, int offset, int numFields)
246 throws CodingException
247 {
248 // Subtract header and possible padding byte (at end) from num fields.
249 int padding = offset % 2;
250 numFields -= (offset + padding) / 2;
251 return decodeCharset(data, offset, numFields, 2, "utf-16be");
252 }
253
254 private static String decodeCharset(byte[] data, int offset, int numFields, int width,
255 String charset) throws CodingException
256 {
257 if (numFields < 0 || (numFields * width + offset) > data.length) {
258 // Try to decode the max number of characters in payload
259 int padding = offset % width;
260 int maxNumFields = (data.length - offset - padding) / width;
261 if (maxNumFields < 0) {
262 throw new CodingException(charset + " decode failed: offset out of range");
263 }
264 Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
265 + numFields + " data.length = " + data.length + " maxNumFields = "
266 + maxNumFields);
267 numFields = maxNumFields;
268 }
269 try {
270 return new String(data, offset, numFields * width, charset);
271 } catch (java.io.UnsupportedEncodingException ex) {
272 throw new CodingException(charset + " decode failed: " + ex);
273 }
274 }
275
276 private static String decode7bitAscii(byte[] data, int offset, int numFields)
277 throws CodingException
278 {
279 try {
280 int offsetBits = offset * 8;
281 int offsetSeptets = (offsetBits + 6) / 7;
282 numFields -= offsetSeptets;
283
284 StringBuffer strBuf = new StringBuffer(numFields);
285 BitwiseInputStream inStream = new BitwiseInputStream(data);
286 int wantedBits = (offsetSeptets * 7) + (numFields * 7);
287 if (inStream.available() < wantedBits) {
288 throw new CodingException("insufficient data (wanted " + wantedBits +
289 " bits, but only have " + inStream.available() + ")");
290 }
291 inStream.skip(offsetSeptets * 7);
292 for (int i = 0; i < numFields; i++) {
293 int charCode = inStream.read(7);
294 if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
295 (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
296 strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
297 } else if (charCode == UserData.ASCII_NL_INDEX) {
298 strBuf.append('\n');
299 } else if (charCode == UserData.ASCII_CR_INDEX) {
300 strBuf.append('\r');
301 } else {
302 /* For other charCodes, they are unprintable, and so simply use SPACE. */
303 strBuf.append(' ');
304 }
305 }
306 return strBuf.toString();
307 } catch (BitwiseInputStream.AccessException ex) {
308 throw new CodingException("7bit ASCII decode failed: " + ex);
309 }
310 }
311
312 private static String decode7bitGsm(byte[] data, int offset, int numFields)
313 throws CodingException
314 {
315 // Start reading from the next 7-bit aligned boundary after offset.
316 int offsetBits = offset * 8;
317 int offsetSeptets = (offsetBits + 6) / 7;
318 numFields -= offsetSeptets;
319 int paddingBits = (offsetSeptets * 7) - offsetBits;
320 String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
321 0, 0);
322 if (result == null) {
323 throw new CodingException("7bit GSM decoding failed");
324 }
325 return result;
326 }
327
328 private static String decodeLatin(byte[] data, int offset, int numFields)
329 throws CodingException
330 {
331 return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
332 }
333
334 private static String decodeShiftJis(byte[] data, int offset, int numFields)
335 throws CodingException
336 {
337 return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
338 }
339
340 private static String decodeGsmDcs(byte[] data, int offset, int numFields, int msgType)
341 throws CodingException
342 {
343 if ((msgType & 0xC0) != 0) {
344 throw new CodingException("unsupported coding group ("
345 + msgType + ")");
346 }
347
348 switch ((msgType >> 2) & 0x3) {
349 case UserData.ENCODING_GSM_DCS_7BIT:
350 return decode7bitGsm(data, offset, numFields);
351 case UserData.ENCODING_GSM_DCS_8BIT:
352 return decodeUtf8(data, offset, numFields);
353 case UserData.ENCODING_GSM_DCS_16BIT:
354 return decodeUtf16(data, offset, numFields);
355 default:
356 throw new CodingException("unsupported user msgType encoding ("
357 + msgType + ")");
358 }
359 }
360
361 private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
362 throws CodingException
363 {
364 int offset = 0;
365 if (hasUserDataHeader) {
366 int udhLen = userData.payload[0] & 0x00FF;
367 offset += udhLen + 1;
368 byte[] headerData = new byte[udhLen];
369 System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
370 userData.userDataHeader = SmsHeader.fromByteArray(headerData);
371 }
372 switch (userData.msgEncoding) {
373 case UserData.ENCODING_OCTET:
374 /*
375 * Octet decoding depends on the carrier service.
376 */
377 boolean decodingtypeUTF8 = Resources.getSystem()
378 .getBoolean(R.bool.config_sms_utf8_support);
379
380 // Strip off any padding bytes, meaning any differences between the length of the
381 // array and the target length specified by numFields. This is to avoid any
382 // confusion by code elsewhere that only considers the payload array length.
383 byte[] payload = new byte[userData.numFields];
384 int copyLen = userData.numFields < userData.payload.length
385 ? userData.numFields : userData.payload.length;
386
387 System.arraycopy(userData.payload, 0, payload, 0, copyLen);
388 userData.payload = payload;
389
390 if (!decodingtypeUTF8) {
391 // There are many devices in the market that send 8bit text sms (latin encoded) as
392 // octet encoded.
393 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
394 } else {
395 userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
396 }
397 break;
398
399 case UserData.ENCODING_IA5:
400 case UserData.ENCODING_7BIT_ASCII:
401 userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
402 break;
403 case UserData.ENCODING_UNICODE_16:
404 userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
405 break;
406 case UserData.ENCODING_GSM_7BIT_ALPHABET:
407 userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
408 break;
409 case UserData.ENCODING_LATIN:
410 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
411 break;
412 case UserData.ENCODING_SHIFT_JIS:
413 userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
414 break;
415 case UserData.ENCODING_GSM_DCS:
416 userData.payloadStr = decodeGsmDcs(userData.payload, offset,
417 userData.numFields, userData.msgType);
418 break;
419 default:
420 throw new CodingException("unsupported user data encoding ("
421 + userData.msgEncoding + ")");
422 }
423 }
424
425 private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
426 throws BitwiseInputStream.AccessException {
427 final int EXPECTED_PARAM_SIZE = 1 * 8;
428 boolean decodeSuccess = false;
429 int paramBits = inStream.read(8) * 8;
430 if (paramBits >= EXPECTED_PARAM_SIZE) {
431 paramBits -= EXPECTED_PARAM_SIZE;
432 decodeSuccess = true;
433 bData.language = inStream.read(8);
434 }
435 if ((! decodeSuccess) || (paramBits > 0)) {
436 Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
437 (decodeSuccess ? "succeeded" : "failed") +
438 " (extra bits = " + paramBits + ")");
439 }
440 inStream.skip(paramBits);
441 return decodeSuccess;
442 }
443
444 private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
445 throws BitwiseInputStream.AccessException {
446 final int EXPECTED_PARAM_SIZE = 1 * 8;
447 boolean decodeSuccess = false;
448 int paramBits = inStream.read(8) * 8;
449 if (paramBits >= EXPECTED_PARAM_SIZE) {
450 paramBits -= EXPECTED_PARAM_SIZE;
451 decodeSuccess = true;
452 bData.priority = inStream.read(2);
453 inStream.skip(6);
454 }
455 if ((! decodeSuccess) || (paramBits > 0)) {
456 Log.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
457 (decodeSuccess ? "succeeded" : "failed") +
458 " (extra bits = " + paramBits + ")");
459 }
460 inStream.skip(paramBits);
461 return decodeSuccess;
462 }
463
464 private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
465 switch (serviceCategory) {
466 case CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
467 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
468
469 case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT:
470 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
471
472 case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT:
473 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
474
475 case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
476 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
477
478 case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE:
479 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
480
481 default:
482 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
483 }
484 }
485
486 /**
487 * CMAS message decoding.
488 * (See TIA-1149-0-1, CMAS over CDMA)
489 *
490 * @param serviceCategory is the service category from the SMS envelope
491 */
492 private static void decodeCmasUserData(BearerData bData, int serviceCategory)
493 throws BitwiseInputStream.AccessException, CodingException {
494 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
495 if (inStream.available() < 8) {
496 throw new CodingException("emergency CB with no CMAE_protocol_version");
497 }
498 int protocolVersion = inStream.read(8);
499 if (protocolVersion != 0) {
500 throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
501 }
502
503 int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
504 int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
505 int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
506 int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
507 int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
508 int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
509
510 while (inStream.available() >= 16) {
511 int recordType = inStream.read(8);
512 int recordLen = inStream.read(8);
513 switch (recordType) {
514 case 0: // Type 0 elements (Alert text)
515 UserData alertUserData = new UserData();
516 alertUserData.msgEncoding = inStream.read(5);
517 alertUserData.msgEncodingSet = true;
518 alertUserData.msgType = 0;
519
520 int numFields; // number of chars to decode
521 switch (alertUserData.msgEncoding) {
522 case UserData.ENCODING_OCTET:
523 case UserData.ENCODING_LATIN:
524 numFields = recordLen - 1; // subtract 1 byte for encoding
525 break;
526
527 case UserData.ENCODING_IA5:
528 case UserData.ENCODING_7BIT_ASCII:
529 case UserData.ENCODING_GSM_7BIT_ALPHABET:
530 numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding
531 break;
532
533 case UserData.ENCODING_UNICODE_16:
534 numFields = (recordLen - 1) / 2;
535 break;
536
537 default:
538 numFields = 0; // unsupported encoding
539 }
540
541 alertUserData.numFields = numFields;
542 alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
543 decodeUserDataPayload(alertUserData, false);
544 bData.userData = alertUserData;
545 break;
546
547 case 1: // Type 1 elements
548 category = inStream.read(8);
549 responseType = inStream.read(8);
550 severity = inStream.read(4);
551 urgency = inStream.read(4);
552 certainty = inStream.read(4);
553 inStream.skip(recordLen * 8 - 28);
554 break;
555
556 default:
557 Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
558 inStream.skip(recordLen * 8);
559 break;
560 }
561 }
562
563 bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
564 urgency, certainty);
565 }
566
567 private static boolean isCmasAlertCategory(int category) {
568 return category >= CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
569 && category <= CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;
570 }
571
572 /**
573 * Create BearerData object from serialized representation.
574 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
575 *
576 * @param smsData byte array of raw encoded SMS bearer data.
577 * @param serviceCategory the envelope service category (for CMAS alert handling)
578 * @return an instance of BearerData.
579 */
580 public static BearerData decode(byte[] smsData, int serviceCategory) {
581 try {
582 BitwiseInputStream inStream = new BitwiseInputStream(smsData);
583 BearerData bData = new BearerData();
584 int foundSubparamMask = 0;
585 while (inStream.available() > 0) {
586 int subparamId = inStream.read(8);
587 int subparamIdBit = 1 << subparamId;
588 // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
589 // as 32th bit is the max bit in int.
590 // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
591 // last defined subparam ID is 23 (00010111 = 0x17 = 23).
592 // Only do duplicate subparam ID check if subparam is within defined value as
593 // reserved subparams are just skipped.
594 if ((foundSubparamMask & subparamIdBit) != 0 &&
595 (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
596 subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
597 throw new CodingException("illegal duplicate subparameter (" +
598 subparamId + ")");
599 }
600 boolean decodeSuccess;
601 switch (subparamId) {
602 case SUBPARAM_MESSAGE_IDENTIFIER:
603 decodeSuccess = decodeMessageId(bData, inStream);
604 break;
605 case SUBPARAM_USER_DATA:
606 decodeSuccess = decodeUserData(bData, inStream);
607 break;
608 case SUBPARAM_LANGUAGE_INDICATOR:
609 decodeSuccess = decodeLanguageIndicator(bData, inStream);
610 break;
611 case SUBPARAM_PRIORITY_INDICATOR:
612 decodeSuccess = decodePriorityIndicator(bData, inStream);
613 break;
614 default:
615 decodeSuccess = decodeReserved(inStream, subparamId);
616 }
617 if (decodeSuccess &&
618 (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
619 subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
620 foundSubparamMask |= subparamIdBit;
621 }
622 }
623 if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
624 throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
625 }
626 if (bData.userData != null) {
627 if (isCmasAlertCategory(serviceCategory)) {
628 decodeCmasUserData(bData, serviceCategory);
629 } else {
630 decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
631 }
632 }
633 return bData;
634 } catch (BitwiseInputStream.AccessException ex) {
635 Log.e(LOG_TAG, "BearerData decode failed: " + ex);
636 } catch (CodingException ex) {
637 Log.e(LOG_TAG, "BearerData decode failed: " + ex);
638 }
639 return null;
640 }
641}