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