1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cellbroadcastservice;
18 
19 import android.telephony.SmsCbCmasInfo;
20 import android.telephony.SmsCbEtwsInfo;
21 import android.telephony.SmsMessage;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.util.Arrays;
26 import java.util.Locale;
27 
28 /**
29  * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
30  * CellBroadcastReceiver test cases, but should not be used by applications.
31  *
32  * All relevant header information is now sent as a Parcelable
33  * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
34  * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
35  * {@link android.provider.Telephony.Sms.Intents#ACTION_SMS_EMERGENCY_CB_RECEIVED} intent.
36  * The raw PDU is no longer sent to SMS CB applications.
37  */
38 public class SmsCbHeader {
39     /**
40      * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
41      */
42     private static final String[] LANGUAGE_CODES_GROUP_0 = {
43             Locale.GERMAN.getLanguage(),        // German
44             Locale.ENGLISH.getLanguage(),       // English
45             Locale.ITALIAN.getLanguage(),       // Italian
46             Locale.FRENCH.getLanguage(),        // French
47             new Locale("es").getLanguage(),     // Spanish
48             new Locale("nl").getLanguage(),     // Dutch
49             new Locale("sv").getLanguage(),     // Swedish
50             new Locale("da").getLanguage(),     // Danish
51             new Locale("pt").getLanguage(),     // Portuguese
52             new Locale("fi").getLanguage(),     // Finnish
53             new Locale("nb").getLanguage(),     // Norwegian
54             new Locale("el").getLanguage(),     // Greek
55             new Locale("tr").getLanguage(),     // Turkish
56             new Locale("hu").getLanguage(),     // Hungarian
57             new Locale("pl").getLanguage(),     // Polish
58             null
59     };
60 
61     /**
62      * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
63      */
64     private static final String[] LANGUAGE_CODES_GROUP_2 = {
65             new Locale("cs").getLanguage(),     // Czech
66             new Locale("he").getLanguage(),     // Hebrew
67             new Locale("ar").getLanguage(),     // Arabic
68             new Locale("ru").getLanguage(),     // Russian
69             new Locale("is").getLanguage(),     // Icelandic
70             null, null, null, null, null, null, null, null, null, null, null
71     };
72 
73     /**
74      * Length of SMS-CB header
75      */
76     public static final int PDU_HEADER_LENGTH = 6;
77 
78     /**
79      * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
80      */
81     static final int FORMAT_GSM = 1;
82 
83     /**
84      * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
85      */
86     static final int FORMAT_UMTS = 2;
87 
88     /**
89      * ETWS pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
90      */
91     static final int FORMAT_ETWS_PRIMARY = 3;
92 
93     /**
94      * Message type value as defined in 3gpp TS 25.324, section 11.1.
95      */
96     private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
97 
98     /**
99      * Length of GSM pdus
100      */
101     private static final int PDU_LENGTH_GSM = 88;
102 
103     /**
104      * Maximum length of ETWS primary message GSM pdus
105      */
106     private static final int PDU_LENGTH_ETWS = 56;
107 
108     private final int mGeographicalScope;
109 
110     /** The serial number combines geographical scope, message code, and update number. */
111     private final int mSerialNumber;
112 
113     /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
114     private final int mMessageIdentifier;
115 
116     private final int mDataCodingScheme;
117 
118     private final int mPageIndex;
119 
120     private final int mNrOfPages;
121 
122     private final int mFormat;
123 
124     private DataCodingScheme mDataCodingSchemeStructedData;
125 
126     /** ETWS warning notification info. */
127     private final SmsCbEtwsInfo mEtwsInfo;
128 
129     /** CMAS warning notification info. */
130     private final SmsCbCmasInfo mCmasInfo;
131 
SmsCbHeader(byte[] pdu)132     public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
133         if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
134             throw new IllegalArgumentException("Illegal PDU");
135         }
136 
137         if (pdu.length <= PDU_LENGTH_GSM) {
138             // can be ETWS or GSM format.
139             // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
140             // contain serial number which contains GS, Message Code, and Update Number
141             // per 9.4.1.2.1, and message identifier in same octets
142             mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
143             mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
144             mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
145             if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
146                 mFormat = FORMAT_ETWS_PRIMARY;
147                 mDataCodingScheme = -1;
148                 mPageIndex = -1;
149                 mNrOfPages = -1;
150                 boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
151                 boolean activatePopup = (pdu[5] & 0x80) != 0;
152                 int warningType = (pdu[4] & 0xfe) >>> 1;
153                 byte[] warningSecurityInfo;
154                 // copy the Warning-Security-Information, if present
155                 if (pdu.length > PDU_HEADER_LENGTH) {
156                     warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
157                 } else {
158                     warningSecurityInfo = null;
159                 }
160                 mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
161                         true, warningSecurityInfo);
162                 mCmasInfo = null;
163                 return;     // skip the ETWS/CMAS initialization code for regular notifications
164             } else {
165                 // GSM pdus are no more than 88 bytes
166                 mFormat = FORMAT_GSM;
167                 mDataCodingScheme = pdu[4] & 0xff;
168 
169                 // Check for invalid page parameter
170                 int pageIndex = (pdu[5] & 0xf0) >>> 4;
171                 int nrOfPages = pdu[5] & 0x0f;
172 
173                 if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
174                     pageIndex = 1;
175                     nrOfPages = 1;
176                 }
177 
178                 mPageIndex = pageIndex;
179                 mNrOfPages = nrOfPages;
180             }
181         } else {
182             // UMTS pdus are always at least 90 bytes since the payload includes
183             // a number-of-pages octet and also one length octet per page
184             mFormat = FORMAT_UMTS;
185 
186             int messageType = pdu[0];
187 
188             if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
189                 throw new IllegalArgumentException("Unsupported message type " + messageType);
190             }
191 
192             mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
193             mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
194             mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
195             mDataCodingScheme = pdu[5] & 0xff;
196 
197             // We will always consider a UMTS message as having one single page
198             // since there's only one instance of the header, even though the
199             // actual payload may contain several pages.
200             mPageIndex = 1;
201             mNrOfPages = 1;
202         }
203 
204         if (mDataCodingScheme != -1) {
205             mDataCodingSchemeStructedData = new DataCodingScheme(mDataCodingScheme);
206         }
207 
208         if (isEtwsMessage()) {
209             boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
210             boolean activatePopup = isEtwsPopupAlert();
211             int warningType = getEtwsWarningType();
212             mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
213                     false, null);
214             mCmasInfo = null;
215         } else if (isCmasMessage()) {
216             int messageClass = getCmasMessageClass();
217             int severity = getCmasSeverity();
218             int urgency = getCmasUrgency();
219             int certainty = getCmasCertainty();
220             mEtwsInfo = null;
221             mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
222                     SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
223         } else {
224             mEtwsInfo = null;
225             mCmasInfo = null;
226         }
227     }
228 
getGeographicalScope()229     public int getGeographicalScope() {
230         return mGeographicalScope;
231     }
232 
getSerialNumber()233     public int getSerialNumber() {
234         return mSerialNumber;
235     }
236 
getServiceCategory()237     public int getServiceCategory() {
238         return mMessageIdentifier;
239     }
240 
getDataCodingScheme()241     public int getDataCodingScheme() {
242         return mDataCodingScheme;
243     }
244 
getDataCodingSchemeStructedData()245     public DataCodingScheme getDataCodingSchemeStructedData() {
246         return mDataCodingSchemeStructedData;
247     }
248 
getPageIndex()249     public int getPageIndex() {
250         return mPageIndex;
251     }
252 
getNumberOfPages()253     public int getNumberOfPages() {
254         return mNrOfPages;
255     }
256 
getEtwsInfo()257     public SmsCbEtwsInfo getEtwsInfo() {
258         return mEtwsInfo;
259     }
260 
getCmasInfo()261     public SmsCbCmasInfo getCmasInfo() {
262         return mCmasInfo;
263     }
264 
265     /**
266      * Return whether this broadcast is an emergency (PWS) message type.
267      * @return true if this message is emergency type; false otherwise
268      */
isEmergencyMessage()269     public boolean isEmergencyMessage() {
270         return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
271                 && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
272     }
273 
274     /**
275      * Return whether this broadcast is an ETWS emergency message type.
276      * @return true if this message is ETWS emergency type; false otherwise
277      */
278     @VisibleForTesting
isEtwsMessage()279     public boolean isEtwsMessage() {
280         return (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
281                 == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
282     }
283 
284     /**
285      * Return whether this broadcast is an ETWS primary notification.
286      * @return true if this message is an ETWS primary notification; false otherwise
287      */
isEtwsPrimaryNotification()288     public boolean isEtwsPrimaryNotification() {
289         return mFormat == FORMAT_ETWS_PRIMARY;
290     }
291 
292     /**
293      * Return whether this broadcast is in UMTS format.
294      * @return true if this message is in UMTS format; false otherwise
295      */
isUmtsFormat()296     public boolean isUmtsFormat() {
297         return mFormat == FORMAT_UMTS;
298     }
299 
300     /**
301      * Return whether this message is a CMAS emergency message type.
302      * @return true if this message is CMAS emergency type; false otherwise
303      */
isCmasMessage()304     private boolean isCmasMessage() {
305         return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
306                 && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
307     }
308 
309     /**
310      * Return whether the popup alert flag is set for an ETWS warning notification.
311      * This method assumes that the message ID has already been checked for ETWS type.
312      *
313      * @return true if the message code indicates a popup alert should be displayed
314      */
isEtwsPopupAlert()315     private boolean isEtwsPopupAlert() {
316         return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
317     }
318 
319     /**
320      * Return whether the emergency user alert flag is set for an ETWS warning notification.
321      * This method assumes that the message ID has already been checked for ETWS type.
322      *
323      * @return true if the message code indicates an emergency user alert
324      */
isEtwsEmergencyUserAlert()325     private boolean isEtwsEmergencyUserAlert() {
326         return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
327     }
328 
329     /**
330      * Returns the warning type for an ETWS warning notification.
331      * This method assumes that the message ID has already been checked for ETWS type.
332      *
333      * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
334      */
getEtwsWarningType()335     private int getEtwsWarningType() {
336         return mMessageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
337     }
338 
339     /**
340      * Returns the message class for a CMAS warning notification.
341      * This method assumes that the message ID has already been checked for CMAS type.
342      * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
343      */
getCmasMessageClass()344     private int getCmasMessageClass() {
345         switch (mMessageIdentifier) {
346             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
347             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
348                 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
349 
350             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
351             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
352             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
353             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
354                 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
355 
356             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
357             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
358             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
359             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
360             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
361             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
362             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
363             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
364             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
365             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
366             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
367             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
368                 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
369 
370             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
371             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
372                 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
373 
374             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
375             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
376                 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
377 
378             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
379             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
380                 return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
381 
382             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
383             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
384                 return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
385 
386             default:
387                 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
388         }
389     }
390 
391     /**
392      * Returns the severity for a CMAS warning notification. This is only available for extreme
393      * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
394      * This method assumes that the message ID has already been checked for CMAS type.
395      * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
396      */
getCmasSeverity()397     private int getCmasSeverity() {
398         switch (mMessageIdentifier) {
399             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
400             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
401             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
402             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
403             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
404             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
405             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
406             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
407                 return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
408 
409             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
410             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
411             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
412             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
413             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
414             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
415             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
416             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
417                 return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
418 
419             default:
420                 return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
421         }
422     }
423 
424     /**
425      * Returns the urgency for a CMAS warning notification. This is only available for extreme
426      * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
427      * This method assumes that the message ID has already been checked for CMAS type.
428      * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
429      */
getCmasUrgency()430     private int getCmasUrgency() {
431         switch (mMessageIdentifier) {
432             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
433             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
434             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
435             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
436             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
437             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
438             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
439             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
440                 return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
441 
442             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
443             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
444             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
445             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
446             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
447             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
448             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
449             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
450                 return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
451 
452             default:
453                 return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
454         }
455     }
456 
457     /**
458      * Returns the certainty for a CMAS warning notification. This is only available for extreme
459      * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
460      * This method assumes that the message ID has already been checked for CMAS type.
461      * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
462      */
getCmasCertainty()463     private int getCmasCertainty() {
464         switch (mMessageIdentifier) {
465             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
466             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
467             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
468             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
469             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
470             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
471             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
472             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
473                 return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
474 
475             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
476             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
477             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
478             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
479             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
480             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
481             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
482             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
483                 return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
484 
485             default:
486                 return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
487         }
488     }
489 
490     @Override
toString()491     public String toString() {
492         return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x"
493                 + Integer.toHexString(mSerialNumber)
494                 + ", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier)
495                 + ", format=" + mFormat
496                 + ", DCS=0x" + Integer.toHexString(mDataCodingScheme)
497                 + ", page " + mPageIndex + " of " + mNrOfPages + '}';
498     }
499 
500     /**
501      * CBS Data Coding Scheme.
502      * Reference: 3GPP TS 23.038 version 15.0.0 section #5, CBS Data Coding Scheme
503      */
504     public static final class DataCodingScheme {
505         public final int encoding;
506         public final String language;
507         public final boolean hasLanguageIndicator;
508 
DataCodingScheme(int dataCodingScheme)509         public DataCodingScheme(int dataCodingScheme) {
510             int encoding = 0;
511             String language = null;
512             boolean hasLanguageIndicator = false;
513 
514             // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
515             // section 5.
516             switch ((dataCodingScheme & 0xf0) >> 4) {
517                 case 0x00:
518                     encoding = SmsMessage.ENCODING_7BIT;
519                     language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
520                     break;
521 
522                 case 0x01:
523                     hasLanguageIndicator = true;
524                     if ((dataCodingScheme & 0x0f) == 0x01) {
525                         encoding = SmsMessage.ENCODING_16BIT;
526                     } else {
527                         encoding = SmsMessage.ENCODING_7BIT;
528                     }
529                     break;
530 
531                 case 0x02:
532                     encoding = SmsMessage.ENCODING_7BIT;
533                     language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
534                     break;
535 
536                 case 0x03:
537                     encoding = SmsMessage.ENCODING_7BIT;
538                     break;
539 
540                 case 0x04:
541                 case 0x05:
542                     switch ((dataCodingScheme & 0x0c) >> 2) {
543                         case 0x01:
544                             encoding = SmsMessage.ENCODING_8BIT;
545                             break;
546 
547                         case 0x02:
548                             encoding = SmsMessage.ENCODING_16BIT;
549                             break;
550 
551                         case 0x00:
552                         default:
553                             encoding = SmsMessage.ENCODING_7BIT;
554                             break;
555                     }
556                     break;
557 
558                 case 0x06:
559                 case 0x07:
560                     // Compression not supported
561                 case 0x09:
562                     // UDH structure not supported
563                 case 0x0e:
564                     // Defined by the WAP forum not supported
565                     throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
566                             + dataCodingScheme);
567 
568                 case 0x0f:
569                     if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
570                         encoding = SmsMessage.ENCODING_8BIT;
571                     } else {
572                         encoding = SmsMessage.ENCODING_7BIT;
573                     }
574                     break;
575 
576                 default:
577                     // Reserved values are to be treated as 7-bit
578                     encoding = SmsMessage.ENCODING_7BIT;
579                     break;
580             }
581 
582 
583             this.encoding = encoding;
584             this.language = language;
585             this.hasLanguageIndicator = hasLanguageIndicator;
586         }
587     }
588 }
589