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             final String errorMessage = "Illegal PDU";
135             CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
136                     CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_HEADER_LENGTH,
137                     errorMessage);
138             throw new IllegalArgumentException(errorMessage);
139         }
140 
141         if (pdu.length <= PDU_LENGTH_GSM) {
142             // can be ETWS or GSM format.
143             // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
144             // contain serial number which contains GS, Message Code, and Update Number
145             // per 9.4.1.2.1, and message identifier in same octets
146             mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
147             mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
148             mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
149             if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
150                 mFormat = FORMAT_ETWS_PRIMARY;
151                 mDataCodingScheme = -1;
152                 mPageIndex = -1;
153                 mNrOfPages = -1;
154                 boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
155                 boolean activatePopup = (pdu[5] & 0x80) != 0;
156                 int warningType = (pdu[4] & 0xfe) >>> 1;
157                 byte[] warningSecurityInfo;
158                 // copy the Warning-Security-Information, if present
159                 if (pdu.length > PDU_HEADER_LENGTH) {
160                     warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
161                 } else {
162                     warningSecurityInfo = null;
163                 }
164                 mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
165                         true, warningSecurityInfo);
166                 mCmasInfo = null;
167                 return;     // skip the ETWS/CMAS initialization code for regular notifications
168             } else {
169                 // GSM pdus are no more than 88 bytes
170                 mFormat = FORMAT_GSM;
171                 mDataCodingScheme = pdu[4] & 0xff;
172 
173                 // Check for invalid page parameter
174                 int pageIndex = (pdu[5] & 0xf0) >>> 4;
175                 int nrOfPages = pdu[5] & 0x0f;
176 
177                 if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
178                     pageIndex = 1;
179                     nrOfPages = 1;
180                 }
181 
182                 mPageIndex = pageIndex;
183                 mNrOfPages = nrOfPages;
184             }
185         } else {
186             // UMTS pdus are always at least 90 bytes since the payload includes
187             // a number-of-pages octet and also one length octet per page
188             mFormat = FORMAT_UMTS;
189 
190             int messageType = pdu[0];
191 
192             if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
193                 IllegalArgumentException ex = new IllegalArgumentException(
194                         "Unsupported message type " + messageType);
195                 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
196                         CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UNSUPPORTED_HEADER_MESSAGE_TYPE,
197                         ex.toString());
198                 throw ex;
199             }
200 
201             mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
202             mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
203             mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
204             mDataCodingScheme = pdu[5] & 0xff;
205 
206             // We will always consider a UMTS message as having one single page
207             // since there's only one instance of the header, even though the
208             // actual payload may contain several pages.
209             mPageIndex = 1;
210             mNrOfPages = 1;
211         }
212 
213         if (mDataCodingScheme != -1) {
214             mDataCodingSchemeStructedData = new DataCodingScheme(mDataCodingScheme);
215         }
216 
217         if (isEtwsMessage()) {
218             boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
219             boolean activatePopup = isEtwsPopupAlert();
220             int warningType = getEtwsWarningType();
221             mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
222                     false, null);
223             mCmasInfo = null;
224         } else if (isCmasMessage()) {
225             int messageClass = getCmasMessageClass();
226             int severity = getCmasSeverity();
227             int urgency = getCmasUrgency();
228             int certainty = getCmasCertainty();
229             mEtwsInfo = null;
230             mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
231                     SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
232         } else {
233             mEtwsInfo = null;
234             mCmasInfo = null;
235         }
236     }
237 
getGeographicalScope()238     public int getGeographicalScope() {
239         return mGeographicalScope;
240     }
241 
getSerialNumber()242     public int getSerialNumber() {
243         return mSerialNumber;
244     }
245 
getServiceCategory()246     public int getServiceCategory() {
247         return mMessageIdentifier;
248     }
249 
getDataCodingScheme()250     public int getDataCodingScheme() {
251         return mDataCodingScheme;
252     }
253 
getDataCodingSchemeStructedData()254     public DataCodingScheme getDataCodingSchemeStructedData() {
255         return mDataCodingSchemeStructedData;
256     }
257 
getPageIndex()258     public int getPageIndex() {
259         return mPageIndex;
260     }
261 
getNumberOfPages()262     public int getNumberOfPages() {
263         return mNrOfPages;
264     }
265 
getEtwsInfo()266     public SmsCbEtwsInfo getEtwsInfo() {
267         return mEtwsInfo;
268     }
269 
getCmasInfo()270     public SmsCbCmasInfo getCmasInfo() {
271         return mCmasInfo;
272     }
273 
274     /**
275      * Return whether this broadcast is an emergency (PWS) message type.
276      * @return true if this message is emergency type; false otherwise
277      */
isEmergencyMessage()278     public boolean isEmergencyMessage() {
279         return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
280                 && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
281     }
282 
283     /**
284      * Return whether this broadcast is an ETWS emergency message type.
285      * @return true if this message is ETWS emergency type; false otherwise
286      */
287     @VisibleForTesting
isEtwsMessage()288     public boolean isEtwsMessage() {
289         return (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
290                 == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
291     }
292 
293     /**
294      * Return whether this broadcast is an ETWS primary notification.
295      * @return true if this message is an ETWS primary notification; false otherwise
296      */
isEtwsPrimaryNotification()297     public boolean isEtwsPrimaryNotification() {
298         return mFormat == FORMAT_ETWS_PRIMARY;
299     }
300 
301     /**
302      * Return whether this broadcast is in UMTS format.
303      * @return true if this message is in UMTS format; false otherwise
304      */
isUmtsFormat()305     public boolean isUmtsFormat() {
306         return mFormat == FORMAT_UMTS;
307     }
308 
309     /**
310      * Return whether this message is a CMAS emergency message type.
311      * @return true if this message is CMAS emergency type; false otherwise
312      */
isCmasMessage()313     private boolean isCmasMessage() {
314         return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
315                 && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
316     }
317 
318     /**
319      * Return whether the popup alert flag is set for an ETWS warning notification.
320      * This method assumes that the message ID has already been checked for ETWS type.
321      *
322      * @return true if the message code indicates a popup alert should be displayed
323      */
isEtwsPopupAlert()324     private boolean isEtwsPopupAlert() {
325         return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
326     }
327 
328     /**
329      * Return whether the emergency user alert flag is set for an ETWS warning notification.
330      * This method assumes that the message ID has already been checked for ETWS type.
331      *
332      * @return true if the message code indicates an emergency user alert
333      */
isEtwsEmergencyUserAlert()334     private boolean isEtwsEmergencyUserAlert() {
335         return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
336     }
337 
338     /**
339      * Returns the warning type for an ETWS warning notification.
340      * This method assumes that the message ID has already been checked for ETWS type.
341      *
342      * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
343      */
getEtwsWarningType()344     private int getEtwsWarningType() {
345         return mMessageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
346     }
347 
348     /**
349      * Returns the message class for a CMAS warning notification.
350      * This method assumes that the message ID has already been checked for CMAS type.
351      * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
352      */
getCmasMessageClass()353     private int getCmasMessageClass() {
354         switch (mMessageIdentifier) {
355             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
356             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
357                 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
358 
359             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
360             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
361             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
362             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
363                 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
364 
365             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
366             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
367             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
368             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
369             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
370             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
371             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
372             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
373             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
374             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
375             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
376             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
377                 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
378 
379             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
380             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
381                 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
382 
383             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
384             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
385                 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
386 
387             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
388             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
389                 return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
390 
391             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
392             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
393                 return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
394 
395             default:
396                 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
397         }
398     }
399 
400     /**
401      * Returns the severity for a CMAS warning notification. This is only available for extreme
402      * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
403      * This method assumes that the message ID has already been checked for CMAS type.
404      * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
405      */
getCmasSeverity()406     private int getCmasSeverity() {
407         switch (mMessageIdentifier) {
408             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
409             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
410             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
411             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
412             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
413             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
414             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
415             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
416                 return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
417 
418             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
419             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
420             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
421             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
422             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
423             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
424             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
425             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
426                 return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
427 
428             default:
429                 return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
430         }
431     }
432 
433     /**
434      * Returns the urgency for a CMAS warning notification. This is only available for extreme
435      * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
436      * This method assumes that the message ID has already been checked for CMAS type.
437      * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
438      */
getCmasUrgency()439     private int getCmasUrgency() {
440         switch (mMessageIdentifier) {
441             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
442             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
443             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
444             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
445             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
446             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
447             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
448             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
449                 return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
450 
451             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
452             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
453             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
454             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
455             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
456             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
457             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
458             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
459                 return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
460 
461             default:
462                 return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
463         }
464     }
465 
466     /**
467      * Returns the certainty for a CMAS warning notification. This is only available for extreme
468      * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
469      * This method assumes that the message ID has already been checked for CMAS type.
470      * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
471      */
getCmasCertainty()472     private int getCmasCertainty() {
473         switch (mMessageIdentifier) {
474             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
475             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
476             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
477             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
478             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
479             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
480             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
481             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
482                 return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
483 
484             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
485             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
486             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
487             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
488             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
489             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
490             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
491             case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
492                 return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
493 
494             default:
495                 return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
496         }
497     }
498 
499     @Override
toString()500     public String toString() {
501         return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x"
502                 + Integer.toHexString(mSerialNumber)
503                 + ", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier)
504                 + ", format=" + mFormat
505                 + ", DCS=0x" + Integer.toHexString(mDataCodingScheme)
506                 + ", page " + mPageIndex + " of " + mNrOfPages + '}';
507     }
508 
509     /**
510      * CBS Data Coding Scheme.
511      * Reference: 3GPP TS 23.038 version 15.0.0 section #5, CBS Data Coding Scheme
512      */
513     public static final class DataCodingScheme {
514         public final int encoding;
515         public final String language;
516         public final boolean hasLanguageIndicator;
517 
DataCodingScheme(int dataCodingScheme)518         public DataCodingScheme(int dataCodingScheme) {
519             int encoding = 0;
520             String language = null;
521             boolean hasLanguageIndicator = false;
522 
523             // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
524             // section 5.
525             switch ((dataCodingScheme & 0xf0) >> 4) {
526                 case 0x00:
527                     encoding = SmsMessage.ENCODING_7BIT;
528                     language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
529                     break;
530 
531                 case 0x01:
532                     hasLanguageIndicator = true;
533                     if ((dataCodingScheme & 0x0f) == 0x01) {
534                         encoding = SmsMessage.ENCODING_16BIT;
535                     } else {
536                         encoding = SmsMessage.ENCODING_7BIT;
537                     }
538                     break;
539 
540                 case 0x02:
541                     encoding = SmsMessage.ENCODING_7BIT;
542                     language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
543                     break;
544 
545                 case 0x03:
546                     encoding = SmsMessage.ENCODING_7BIT;
547                     break;
548 
549                 case 0x04:
550                 case 0x05:
551                     switch ((dataCodingScheme & 0x0c) >> 2) {
552                         case 0x01:
553                             encoding = SmsMessage.ENCODING_8BIT;
554                             break;
555 
556                         case 0x02:
557                             encoding = SmsMessage.ENCODING_16BIT;
558                             break;
559 
560                         case 0x00:
561                         default:
562                             encoding = SmsMessage.ENCODING_7BIT;
563                             break;
564                     }
565                     break;
566 
567                 case 0x06:
568                 case 0x07:
569                     // Compression not supported
570                 case 0x09:
571                     // UDH structure not supported
572                 case 0x0e:
573                     // Defined by the WAP forum not supported
574                     final String errorMessage =
575                             "Unsupported GSM dataCodingScheme " + dataCodingScheme;
576                     CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
577                             CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UNSUPPORTED_HEADER_DATA_CODING_SCHEME,
578                             errorMessage);
579                     throw new IllegalArgumentException(errorMessage);
580 
581                 case 0x0f:
582                     if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
583                         encoding = SmsMessage.ENCODING_8BIT;
584                     } else {
585                         encoding = SmsMessage.ENCODING_7BIT;
586                     }
587                     break;
588 
589                 default:
590                     // Reserved values are to be treated as 7-bit
591                     encoding = SmsMessage.ENCODING_7BIT;
592                     break;
593             }
594 
595 
596             this.encoding = encoding;
597             this.language = language;
598             this.hasLanguageIndicator = hasLanguageIndicator;
599         }
600     }
601 }
602