• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.tests;
18 
19 import android.content.Context;
20 import android.hardware.radio.V1_0.CdmaSmsMessage;
21 import android.telephony.SmsCbCmasInfo;
22 import android.telephony.SmsCbMessage;
23 import android.telephony.cdma.CdmaSmsCbProgramData;
24 import android.testing.AndroidTestingRunner;
25 import android.testing.TestableLooper;
26 import android.util.Log;
27 
28 import com.android.cellbroadcastservice.BearerData;
29 import com.android.cellbroadcastservice.DefaultCellBroadcastService;
30 import com.android.internal.telephony.GsmAlphabet;
31 import com.android.internal.telephony.cdma.SmsMessage;
32 import com.android.internal.telephony.cdma.SmsMessageConverter;
33 import com.android.internal.util.BitwiseOutputStream;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Ignore;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 
41 import java.util.Arrays;
42 import java.util.List;
43 import java.util.Random;
44 
45 /**
46  * Test cases to verify that our parseCdmaBroadcastSms function correctly works with the
47  * CdmaSmsMessage class.
48  */
49 @RunWith(AndroidTestingRunner.class)
50 @TestableLooper.RunWithLooper
51 public class CdmaSmsMessageTest extends CellBroadcastServiceTestBase {
52 
53     private static final String TAG = "CdmaSmsMessageTest";
54 
55     /* Copy of private subparameter identifier constants from BearerData class. */
56     private static final byte SUBPARAM_MESSAGE_IDENTIFIER = (byte) 0x00;
57     private static final byte SUBPARAM_USER_DATA = (byte) 0x01;
58     private static final byte SUBPARAM_PRIORITY_INDICATOR = (byte) 0x08;
59     private static final byte SUBPARAM_LANGUAGE_INDICATOR = (byte) 0x0D;
60     private static final byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
61 
62     private static final int TELESERVICE_NOT_SET = 0x0000;
63     private static final int TELESERVICE_SCPT = 0x1006;
64 
65     /**
66      * Digit Mode Indicator is a 1-bit value that indicates whether
67      * the address digits are 4-bit DTMF codes or 8-bit codes.  (See
68      * 3GPP2 C.S0015-B, v2, 3.4.3.3)
69      */
70     private static final int DIGIT_MODE_4BIT_DTMF = 0x00;
71     private static final int DIGIT_MODE_8BIT_CHAR = 0x01;
72 
73     /**
74      * Number Mode Indicator is 1-bit value that indicates whether the
75      * address type is a data network address or not.  (See 3GPP2
76      * C.S0015-B, v2, 3.4.3.3)
77      */
78     private static final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00;
79     private static final int NUMBER_MODE_DATA_NETWORK = 0x01;
80 
81     /**
82      * Number Types for data networks.
83      * (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table)
84      * (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset)
85      * NOTE: value is stored in the parent class ton field.
86      */
87     private static final int TON_UNKNOWN = 0x00;
88 
89     /**
90      * Numbering Plan identification is a 0 or 4-bit value that
91      * indicates which numbering plan identification is set.  (See
92      * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3)
93      */
94     private static final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1;
95 
96     /**
97      * User data encoding types.
98      * (See 3GPP2 C.R1001-F, v1.0, table 9.1-1)
99      */
100     public static final int ENCODING_OCTET = 0x00;
101     public static final int ENCODING_IS91_EXTENDED_PROTOCOL = 0x01;
102     public static final int ENCODING_7BIT_ASCII = 0x02;
103     public static final int ENCODING_IA5 = 0x03;
104     public static final int ENCODING_UNICODE_16 = 0x04;
105     public static final int ENCODING_SHIFT_JIS = 0x05;
106     public static final int ENCODING_KOREAN = 0x06;
107     public static final int ENCODING_LATIN_HEBREW = 0x07;
108     public static final int ENCODING_LATIN = 0x08;
109     public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09;
110     public static final int ENCODING_GSM_DCS = 0x0A;
111 
112     /**
113      * IS-91 message types.
114      * (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3)
115      */
116     public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS = 0x82;
117     public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83;
118     public static final int IS91_MSG_TYPE_CLI = 0x84;
119     public static final int IS91_MSG_TYPE_SHORT_MESSAGE = 0x85;
120 
121     /**
122      * Supported message types for CDMA SMS messages
123      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
124      */
125     public static final int MESSAGE_TYPE_DELIVER = 0x01;
126     public static final int MESSAGE_TYPE_SUBMIT = 0x02;
127     public static final int MESSAGE_TYPE_CANCELLATION = 0x03;
128     public static final int MESSAGE_TYPE_DELIVERY_ACK = 0x04;
129 
130     @Before
setUp()131     public void setUp() throws Exception {
132         super.setUp();
133         putResources(com.android.cellbroadcastservice.R.bool.config_sms_utf8_support, false);
134     }
135 
136     @After
tearDown()137     public void tearDown() throws Exception {
138         super.tearDown();
139     }
140 
141     /**
142      * Initialize a Parcel for an incoming CDMA cell broadcast. The caller will write the
143      * bearer data and then convert it to an SmsMessage.
144      *
145      * @param serviceCategory the CDMA service category
146      * @return the initialized Parcel
147      */
createBroadcastParcel(int serviceCategory)148     private static CdmaSmsMessage createBroadcastParcel(int serviceCategory) {
149         CdmaSmsMessage msg = new CdmaSmsMessage();
150 
151         msg.teleserviceId = TELESERVICE_NOT_SET;
152         msg.isServicePresent = true;
153         msg.serviceCategory = serviceCategory;
154 
155         // dummy address (RIL may generate a different dummy address for broadcasts)
156         msg.address.digitMode = DIGIT_MODE_4BIT_DTMF;
157         msg.address.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
158         msg.address.numberType = TON_UNKNOWN;
159         msg.address.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY;
160         msg.subAddress.subaddressType = 0;
161         msg.subAddress.odd = false;
162         return msg;
163     }
164 
165     /**
166      * Initialize a BitwiseOutputStream with the CDMA bearer data subparameters except for
167      * user data. The caller will append the user data and add it to the parcel.
168      *
169      * @param messageId the 16-bit message identifier
170      * @param priority  message priority
171      * @param language  message language code
172      * @return the initialized BitwiseOutputStream
173      */
createBearerDataStream(int messageId, int priority, int language)174     private static BitwiseOutputStream createBearerDataStream(int messageId, int priority,
175             int language) throws BitwiseOutputStream.AccessException {
176         BitwiseOutputStream bos = new BitwiseOutputStream(10);
177         bos.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
178         bos.write(8, 3);    // length: 3 bytes
179         bos.write(4, BearerData.MESSAGE_TYPE_DELIVER);
180         bos.write(8, ((messageId >>> 8) & 0xff));
181         bos.write(8, (messageId & 0xff));
182         bos.write(1, 0);    // no User Data Header
183         bos.write(3, 0);    // reserved
184 
185         if (priority != -1) {
186             bos.write(8, SUBPARAM_PRIORITY_INDICATOR);
187             bos.write(8, 1);    // length: 1 byte
188             bos.write(2, (priority & 0x03));
189             bos.write(6, 0);    // reserved
190         }
191 
192         if (language != -1) {
193             bos.write(8, SUBPARAM_LANGUAGE_INDICATOR);
194             bos.write(8, 1);    // length: 1 byte
195             bos.write(8, (language & 0xff));
196         }
197 
198         return bos;
199     }
200 
201     /**
202      * Write the bearer data array to the parcel, then return a new SmsMessage from the parcel.
203      *
204      * @param msg        CdmaSmsMessage containing the CDMA SMS headers
205      * @param bearerData the bearer data byte array to append to the parcel
206      * @return the new SmsMessage created from the parcel
207      */
createMessageFromParcel(CdmaSmsMessage msg, byte[] bearerData)208     private static SmsMessage createMessageFromParcel(CdmaSmsMessage msg, byte[] bearerData) {
209         for (byte b : bearerData) {
210             msg.bearerData.add(b);
211         }
212         SmsMessage message = SmsMessageConverter.newCdmaSmsMessageFromRil(msg);
213         return message;
214     }
215 
216     /**
217      * Create a parcel for an incoming CMAS broadcast, then return a new SmsMessage created
218      * from the parcel.
219      *
220      * @param serviceCategory the CDMA service category
221      * @param messageId       the 16-bit message identifier
222      * @param priority        message priority
223      * @param language        message language code
224      * @param body            message body
225      * @param cmasCategory    CMAS category (or -1 to skip adding CMAS type 1 elements record)
226      * @param responseType    CMAS response type
227      * @param severity        CMAS severity
228      * @param urgency         CMAS urgency
229      * @param certainty       CMAS certainty
230      * @return the newly created SmsMessage object
231      */
createCmasSmsMessage(int serviceCategory, int messageId, int priority, int language, int encoding, String body, int cmasCategory, int responseType, int severity, int urgency, int certainty)232     private static SmsMessage createCmasSmsMessage(int serviceCategory, int messageId, int priority,
233             int language, int encoding, String body, int cmasCategory, int responseType,
234             int severity, int urgency, int certainty) throws Exception {
235         BitwiseOutputStream cmasBos = new BitwiseOutputStream(10);
236         cmasBos.write(8, 0);    // CMAE protocol version 0
237 
238         if (body != null) {
239             cmasBos.write(8, 0);        // Type 0 elements (alert text)
240             encodeBody(encoding, body, true, cmasBos);
241         }
242 
243         if (cmasCategory != SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN) {
244             cmasBos.write(8, 1);    // Type 1 elements
245             cmasBos.write(8, 4);    // length: 4 bytes
246             cmasBos.write(8, (cmasCategory & 0xff));
247             cmasBos.write(8, (responseType & 0xff));
248             cmasBos.write(4, (severity & 0x0f));
249             cmasBos.write(4, (urgency & 0x0f));
250             cmasBos.write(4, (certainty & 0x0f));
251             cmasBos.write(4, 0);    // pad to octet boundary
252         }
253 
254         byte[] cmasUserData = cmasBos.toByteArray();
255 
256         CdmaSmsMessage msg = createBroadcastParcel(serviceCategory);
257         BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language);
258 
259         bos.write(8, SUBPARAM_USER_DATA);
260         bos.write(8, cmasUserData.length + 2);  // add 2 bytes for msg_encoding and num_fields
261         bos.write(5, ENCODING_OCTET);
262         bos.write(8, cmasUserData.length);
263         bos.writeByteArray(cmasUserData.length * 8, cmasUserData);
264         bos.write(3, 0);    // pad to byte boundary
265 
266         return createMessageFromParcel(msg, bos.toByteArray());
267     }
268 
269     /**
270      * Create a parcel for an incoming CDMA cell broadcast, then return a new SmsMessage created
271      * from the parcel.
272      *
273      * @param serviceCategory the CDMA service category
274      * @param messageId       the 16-bit message identifier
275      * @param priority        message priority
276      * @param language        message language code
277      * @param encoding        user data encoding method
278      * @param body            the message body
279      * @return the newly created SmsMessage object
280      */
createBroadcastSmsMessage(int serviceCategory, int messageId, int priority, int language, int encoding, String body)281     private static SmsMessage createBroadcastSmsMessage(int serviceCategory, int messageId,
282             int priority, int language, int encoding, String body) throws Exception {
283         CdmaSmsMessage msg = createBroadcastParcel(serviceCategory);
284         BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language);
285 
286         bos.write(8, SUBPARAM_USER_DATA);
287         encodeBody(encoding, body, false, bos);
288 
289         return createMessageFromParcel(msg, bos.toByteArray());
290     }
291 
292     /**
293      * Append the message length, encoding, and body to the BearerData output stream.
294      * This is used for writing the User Data subparameter for non-CMAS broadcasts and for
295      * writing the alert text for CMAS broadcasts.
296      *
297      * @param encoding     one of the CDMA UserData encoding values
298      * @param body         the message body
299      * @param isCmasRecord true if this is a CMAS type 0 elements record; false for user data
300      * @param bos          the BitwiseOutputStream to write to
301      * @throws Exception on any encoding error
302      */
encodeBody(int encoding, String body, boolean isCmasRecord, BitwiseOutputStream bos)303     private static void encodeBody(int encoding, String body, boolean isCmasRecord,
304             BitwiseOutputStream bos) throws Exception {
305         if (encoding == ENCODING_7BIT_ASCII || encoding == ENCODING_IA5) {
306             int charCount = body.length();
307             int recordBits = (charCount * 7) + 5;       // add 5 bits for char set field
308             int recordOctets = (recordBits + 7) / 8;    // round up to octet boundary
309             int padBits = (recordOctets * 8) - recordBits;
310 
311             if (!isCmasRecord) {
312                 recordOctets++;                         // add 8 bits for num_fields
313             }
314 
315             bos.write(8, recordOctets);
316             bos.write(5, (encoding & 0x1f));
317 
318             if (!isCmasRecord) {
319                 bos.write(8, charCount);
320             }
321 
322             for (int i = 0; i < charCount; i++) {
323                 bos.write(7, body.charAt(i));
324             }
325 
326             bos.write(padBits, 0);      // pad to octet boundary
327         } else if (encoding == ENCODING_GSM_7BIT_ALPHABET
328                 || encoding == ENCODING_GSM_DCS) {
329             // convert to 7-bit packed encoding with septet count in index 0 of byte array
330             byte[] encodedBody = GsmAlphabet.stringToGsm7BitPacked(body);
331 
332             int charCount = encodedBody[0];             // septet count
333             int recordBits = (charCount * 7) + 5;       // add 5 bits for char set field
334             int recordOctets = (recordBits + 7) / 8;    // round up to octet boundary
335             int padBits = (recordOctets * 8) - recordBits;
336 
337             if (!isCmasRecord) {
338                 recordOctets++;                         // add 8 bits for num_fields
339                 if (encoding == ENCODING_GSM_DCS) {
340                     recordOctets++;                     // add 8 bits for DCS (message type)
341                 }
342             }
343 
344             bos.write(8, recordOctets);
345             bos.write(5, (encoding & 0x1f));
346 
347             if (!isCmasRecord && encoding == ENCODING_GSM_DCS) {
348                 bos.write(8, 0);        // GSM DCS: 7 bit default alphabet, no msg class
349             }
350 
351             if (!isCmasRecord) {
352                 bos.write(8, charCount);
353             }
354             byte[] bodySeptets = Arrays.copyOfRange(encodedBody, 1, encodedBody.length);
355             bos.writeByteArray(charCount * 7, bodySeptets);
356             bos.write(padBits, 0);      // pad to octet boundary
357         } else if (encoding == ENCODING_IS91_EXTENDED_PROTOCOL) {
358             // 6 bit packed encoding with 0x20 offset (ASCII 0x20 - 0x60)
359             int charCount = body.length();
360             int recordBits = (charCount * 6) + 21;      // add 21 bits for header fields
361             int recordOctets = (recordBits + 7) / 8;    // round up to octet boundary
362             int padBits = (recordOctets * 8) - recordBits;
363 
364             bos.write(8, recordOctets);
365 
366             bos.write(5, (encoding & 0x1f));
367             bos.write(8, IS91_MSG_TYPE_SHORT_MESSAGE);
368             bos.write(8, charCount);
369 
370             for (int i = 0; i < charCount; i++) {
371                 bos.write(6, ((int) body.charAt(i) - 0x20));
372             }
373 
374             bos.write(padBits, 0);      // pad to octet boundary
375         } else {
376             byte[] encodedBody;
377             switch (encoding) {
378                 case ENCODING_UNICODE_16:
379                     encodedBody = body.getBytes("UTF-16BE");
380                     break;
381 
382                 case ENCODING_SHIFT_JIS:
383                     encodedBody = body.getBytes("Shift_JIS");
384                     break;
385 
386                 case ENCODING_KOREAN:
387                     encodedBody = body.getBytes("KSC5601");
388                     break;
389 
390                 case ENCODING_LATIN_HEBREW:
391                     encodedBody = body.getBytes("ISO-8859-8");
392                     break;
393 
394                 case ENCODING_LATIN:
395                 default:
396                     encodedBody = body.getBytes("ISO-8859-1");
397                     break;
398             }
399             int charCount = body.length();              // use actual char count for num fields
400             int recordOctets = encodedBody.length + 1;  // add 1 byte for encoding and pad bits
401             if (!isCmasRecord) {
402                 recordOctets++;                         // add 8 bits for num_fields
403             }
404             bos.write(8, recordOctets);
405             bos.write(5, (encoding & 0x1f));
406             if (!isCmasRecord) {
407                 bos.write(8, charCount);
408             }
409             bos.writeByteArray(encodedBody.length * 8, encodedBody);
410             bos.write(3, 0);            // pad to octet boundary
411         }
412     }
413 
414     private static final String TEST_TEXT = "This is a test CDMA cell broadcast message..."
415             + "678901234567890123456789012345678901234567890";
416 
417     private static final String PRES_ALERT =
418             "THE PRESIDENT HAS ISSUED AN EMERGENCY ALERT. CHECK LOCAL MEDIA FOR MORE DETAILS";
419 
420     private static final String EXTREME_ALERT = "FLASH FLOOD WARNING FOR SOUTH COCONINO COUNTY"
421             + " - NORTH CENTRAL ARIZONA UNTIL 415 PM MST";
422 
423     private static final String SEVERE_ALERT = "SEVERE WEATHER WARNING FOR SOMERSET COUNTY"
424             + " - NEW JERSEY UNTIL 415 PM MST";
425 
426     private static final String AMBER_ALERT =
427             "AMBER ALERT:Mountain View,CA VEH'07 Blue Honda Civic CA LIC 5ABC123";
428 
429     private static final String MONTHLY_TEST_ALERT = "This is a test of the emergency alert system."
430             + " This is only a test. 89012345678901234567890";
431 
432     private static final String IS91_TEXT = "IS91 SHORT MSG";   // max length 14 chars
433 
434     /**
435      * Verify that the SmsCbMessage has the correct values for CDMA.
436      *
437      * @param cbMessage the message to test
438      */
verifyCbValues(SmsCbMessage cbMessage)439     private static void verifyCbValues(SmsCbMessage cbMessage) {
440         assertEquals(SmsCbMessage.MESSAGE_FORMAT_3GPP2, cbMessage.getMessageFormat());
441         assertEquals(SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, cbMessage.getGeographicalScope());
442         assertEquals(false, cbMessage.isEtwsMessage()); // ETWS on CDMA not currently supported
443     }
444 
doTestNonEmergencyBroadcast(Context context, int encoding)445     private static void doTestNonEmergencyBroadcast(Context context, int encoding)
446             throws Exception {
447         SmsMessage msg = createBroadcastSmsMessage(123, 456, BearerData.PRIORITY_NORMAL,
448                 BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT);
449 
450         SmsCbMessage cbMessage =
451                 DefaultCellBroadcastService.parseCdmaBroadcastSms(context,
452                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
453         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
454         verifyCbValues(cbMessage);
455         assertEquals(123, cbMessage.getServiceCategory());
456         assertEquals(456, cbMessage.getSerialNumber());
457         assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority());
458         assertEquals("en", cbMessage.getLanguageCode());
459         assertEquals(TEST_TEXT, cbMessage.getMessageBody());
460         assertEquals(false, cbMessage.isEmergencyMessage());
461         assertEquals(false, cbMessage.isCmasMessage());
462     }
463 
464     @Test
testNonEmergencyBroadcast7bitAscii()465     public void testNonEmergencyBroadcast7bitAscii() throws Exception {
466         doTestNonEmergencyBroadcast(mMockedContext, ENCODING_7BIT_ASCII);
467     }
468 
469     @Test
testNonEmergencyBroadcast7bitGsm()470     public void testNonEmergencyBroadcast7bitGsm() throws Exception {
471         doTestNonEmergencyBroadcast(mMockedContext, ENCODING_GSM_7BIT_ALPHABET);
472     }
473 
474     @Test
testNonEmergencyBroadcast16bitUnicode()475     public void testNonEmergencyBroadcast16bitUnicode() throws Exception {
476         doTestNonEmergencyBroadcast(mMockedContext, ENCODING_UNICODE_16);
477     }
478 
doTestCmasBroadcast(Context context, int serviceCategory, int messageClass, String body)479     private static void doTestCmasBroadcast(Context context, int serviceCategory, int messageClass,
480             String body) throws Exception {
481         SmsMessage msg = createCmasSmsMessage(
482                 serviceCategory, 1234, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
483                 ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1);
484 
485         SmsCbMessage cbMessage =
486                 DefaultCellBroadcastService.parseCdmaBroadcastSms(context,
487                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
488         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
489         verifyCbValues(cbMessage);
490         assertEquals(serviceCategory, cbMessage.getServiceCategory());
491         assertEquals(1234, cbMessage.getSerialNumber());
492         assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
493         assertEquals("en", cbMessage.getLanguageCode());
494         assertEquals(body, cbMessage.getMessageBody());
495         assertEquals(true, cbMessage.isEmergencyMessage());
496         assertEquals(true, cbMessage.isCmasMessage());
497         SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
498         assertEquals(messageClass, cmasInfo.getMessageClass());
499         assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory());
500         assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType());
501         assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity());
502         assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency());
503         assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty());
504     }
505 
506     @Test
testCmasPresidentialAlert()507     public void testCmasPresidentialAlert() throws Exception {
508         doTestCmasBroadcast(mMockedContext,
509                 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
510                 SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, PRES_ALERT);
511     }
512 
513     @Test
testCmasExtremeAlert()514     public void testCmasExtremeAlert() throws Exception {
515         doTestCmasBroadcast(mMockedContext, CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
516                 SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, EXTREME_ALERT);
517     }
518 
519     @Test
testCmasSevereAlert()520     public void testCmasSevereAlert() throws Exception {
521         doTestCmasBroadcast(mMockedContext, CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT,
522                 SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT, SEVERE_ALERT);
523     }
524 
525     @Test
testCmasAmberAlert()526     public void testCmasAmberAlert() throws Exception {
527         doTestCmasBroadcast(mMockedContext,
528                 CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
529                 SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY, AMBER_ALERT);
530     }
531 
532     @Test
testCmasTestMessage()533     public void testCmasTestMessage() throws Exception {
534         doTestCmasBroadcast(mMockedContext, CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE,
535                 SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST, MONTHLY_TEST_ALERT);
536     }
537 
538     @Test
testCmasExtremeAlertType1Elements()539     public void testCmasExtremeAlertType1Elements() throws Exception {
540         SmsMessage msg = createCmasSmsMessage(CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
541                 5678, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
542                 ENCODING_7BIT_ASCII, EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_ENV,
543                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE,
544                 SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
545 
546         SmsCbMessage cbMessage =
547                 DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
548                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
549         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
550         verifyCbValues(cbMessage);
551         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
552                 cbMessage.getServiceCategory());
553         assertEquals(5678, cbMessage.getSerialNumber());
554         assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
555         assertEquals("en", cbMessage.getLanguageCode());
556         assertEquals(EXTREME_ALERT, cbMessage.getMessageBody());
557         assertEquals(true, cbMessage.isEmergencyMessage());
558         assertEquals(true, cbMessage.isCmasMessage());
559         SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
560         assertEquals(SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, cmasInfo.getMessageClass());
561         assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_ENV, cmasInfo.getCategory());
562         assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, cmasInfo.getResponseType());
563         assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, cmasInfo.getSeverity());
564         assertEquals(SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, cmasInfo.getUrgency());
565         assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY, cmasInfo.getCertainty());
566     }
567 
568     // VZW requirement is to discard message with unsupported charset. Verify that we return null
569     // for this unsupported character set.
570     @Ignore
571     @Test
testCmasUnsupportedCharSet()572     public void testCmasUnsupportedCharSet() throws Exception {
573         SmsMessage msg = createCmasSmsMessage(CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
574                 12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
575                 0x1F, EXTREME_ALERT, -1, -1, -1, -1, -1);
576 
577         SmsCbMessage cbMessage =
578                 DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
579                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
580         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
581         assertNull("expected null for unsupported charset", cbMessage);
582     }
583 
584     // VZW requirement is to discard message with unsupported charset. Verify that we return null
585     // for this unsupported character set.
586     @Test
testCmasUnsupportedCharSet2()587     public void testCmasUnsupportedCharSet2() throws Exception {
588         SmsMessage msg = createCmasSmsMessage(CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
589                 67890, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
590                 ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1);
591 
592         SmsCbMessage cbMessage =
593                 DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
594                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
595         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
596         assertNull("expected null for unsupported charset", cbMessage);
597     }
598 
599     // VZW requirement is to discard message without record type 0. The framework will decode it
600     // and the app will discard it.
601     @Test
testCmasNoRecordType0()602     public void testCmasNoRecordType0() throws Exception {
603         SmsMessage msg = createCmasSmsMessage(
604                 CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 1234,
605                 BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
606                 ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1);
607 
608         SmsCbMessage cbMessage =
609                 DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
610                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
611         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
612         verifyCbValues(cbMessage);
613         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
614                 cbMessage.getServiceCategory());
615         assertEquals(1234, cbMessage.getSerialNumber());
616         assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
617         assertEquals("en", cbMessage.getLanguageCode());
618         assertEquals(null, cbMessage.getMessageBody());
619         assertEquals(true, cbMessage.isEmergencyMessage());
620         assertEquals(true, cbMessage.isCmasMessage());
621         SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
622         assertEquals(SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, cmasInfo.getMessageClass());
623         assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory());
624         assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType());
625         assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity());
626         assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency());
627         assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty());
628     }
629 
630     // Make sure we don't throw an exception if we feed completely random data to BearerStream.
631     @Test
testRandomBearerStreamData()632     public void testRandomBearerStreamData() {
633         Random r = new Random(54321);
634         for (int run = 0; run < 1000; run++) {
635             int len = r.nextInt(140);
636             byte[] data = new byte[len];
637             for (int i = 0; i < len; i++) {
638                 data[i] = (byte) r.nextInt(256);
639             }
640             // Log.d(TAG, "trying random bearer data run " + run + " length " + len);
641             try {
642                 int category = 0x0ff0 + r.nextInt(32);  // half CMAS, half non-CMAS
643                 CdmaSmsMessage cdmaSmsMessage = createBroadcastParcel(category);
644                 SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, data);
645                 SmsCbMessage cbMessage =
646                         DefaultCellBroadcastService.parseCdmaBroadcastSms(
647                                 mMockedContext,
648                                 0, "", msg.getEnvelopeBearerData(),
649                                 msg.getEnvelopeServiceCategory());
650                 //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
651                 // with random input, cbMessage will almost always be null (log when it isn't)
652                 if (cbMessage != null) {
653                     Log.d(TAG, "success: " + cbMessage);
654                 }
655             } catch (Exception e) {
656                 Log.d(TAG, "exception thrown", e);
657                 fail("Exception in decoder at run " + run + " length " + len + ": " + e);
658             }
659         }
660     }
661 
662     // Make sure we don't throw an exception if we put random data in the UserData subparam.
663     @Test
testRandomUserData()664     public void testRandomUserData() {
665         Random r = new Random(94040);
666         for (int run = 0; run < 1000; run++) {
667             int category = 0x0ff0 + r.nextInt(32);  // half CMAS, half non-CMAS
668             CdmaSmsMessage cdmaSmsMessage = createBroadcastParcel(category);
669             int len = r.nextInt(140);
670             // Log.d(TAG, "trying random user data run " + run + " length " + len);
671 
672             try {
673                 BitwiseOutputStream bos = createBearerDataStream(r.nextInt(65536), r.nextInt(4),
674                         r.nextInt(256));
675 
676                 bos.write(8, SUBPARAM_USER_DATA);
677                 bos.write(8, len);
678 
679                 for (int i = 0; i < len; i++) {
680                     bos.write(8, r.nextInt(256));
681                 }
682 
683                 SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, bos.toByteArray());
684                 SmsCbMessage cbMessage =
685                         DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext, 0, "",
686                                 msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
687                 //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
688             } catch (Exception e) {
689                 Log.d(TAG, "exception thrown", e);
690                 fail("Exception in decoder at run " + run + " length " + len + ": " + e);
691             }
692         }
693     }
694 
695     /**
696      * Initialize a Parcel for incoming Service Category Program Data teleservice. The caller will
697      * write the bearer data and then convert it to an SmsMessage.
698      *
699      * @return the initialized Parcel
700      */
createServiceCategoryProgramDataParcel()701     private static CdmaSmsMessage createServiceCategoryProgramDataParcel() {
702         CdmaSmsMessage msg = new CdmaSmsMessage();
703 
704         msg.teleserviceId = TELESERVICE_SCPT;
705         msg.isServicePresent = false;
706         msg.serviceCategory = 0;
707 
708         // dummy address (RIL may generate a different dummy address for broadcasts)
709         msg.address.digitMode = DIGIT_MODE_4BIT_DTMF;
710         msg.address.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
711         msg.address.numberType = TON_UNKNOWN;
712         msg.address.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY;
713         msg.subAddress.subaddressType = 0;
714         msg.subAddress.odd = false;
715         return msg;
716     }
717 
718     private static final String CAT_EXTREME_THREAT = "Extreme Threat to Life and Property";
719     private static final String CAT_SEVERE_THREAT = "Severe Threat to Life and Property";
720     private static final String CAT_AMBER_ALERTS = "AMBER Alerts";
721 
722     @Test
testServiceCategoryProgramDataAddCategory()723     public void testServiceCategoryProgramDataAddCategory() throws Exception {
724         CdmaSmsMessage cdmaSmsMessage = createServiceCategoryProgramDataParcel();
725         BitwiseOutputStream bos = createBearerDataStream(123, -1, -1);
726 
727         int categoryNameLength = CAT_EXTREME_THREAT.length();
728         int subparamLengthBits = (53 + (categoryNameLength * 7));
729         int subparamLengthBytes = (subparamLengthBits + 7) / 8;
730         int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
731 
732         bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
733         bos.write(8, subparamLengthBytes);
734         bos.write(5, ENCODING_7BIT_ASCII);
735 
736         bos.write(4, CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY);
737         bos.write(8, (CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT >>> 8));
738         bos.write(8, (CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT & 0xff));
739         bos.write(8, BearerData.LANGUAGE_ENGLISH);
740         bos.write(8, 100);  // max messages
741         bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT);
742 
743         bos.write(8, categoryNameLength);
744         for (int i = 0; i < categoryNameLength; i++) {
745             bos.write(7, CAT_EXTREME_THREAT.charAt(i));
746         }
747         bos.write(subparamPadBits, 0);
748 
749         SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, bos.toByteArray());
750         assertNotNull(msg);
751         msg.parseSms();
752         List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
753         assertNotNull(programDataList);
754         assertEquals(1, programDataList.size());
755         CdmaSmsCbProgramData programData = programDataList.get(0);
756         assertEquals(CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY, programData.getOperation());
757         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
758                 programData.getCategory());
759         assertEquals(CAT_EXTREME_THREAT, programData.getCategoryName());
760         assertEquals(BearerData.LANGUAGE_ENGLISH, programData.getLanguage());
761         assertEquals(100, programData.getMaxMessages());
762         assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT, programData.getAlertOption());
763     }
764 
765     @Test
testServiceCategoryProgramDataDeleteTwoCategories()766     public void testServiceCategoryProgramDataDeleteTwoCategories() throws Exception {
767         CdmaSmsMessage cdmaSmsMessage = createServiceCategoryProgramDataParcel();
768         BitwiseOutputStream bos = createBearerDataStream(456, -1, -1);
769 
770         int category1NameLength = CAT_SEVERE_THREAT.length();
771         int category2NameLength = CAT_AMBER_ALERTS.length();
772 
773         int subparamLengthBits = (101 + (category1NameLength * 7) + (category2NameLength * 7));
774         int subparamLengthBytes = (subparamLengthBits + 7) / 8;
775         int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
776 
777         bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
778         bos.write(8, subparamLengthBytes);
779         bos.write(5, ENCODING_7BIT_ASCII);
780 
781         bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
782         bos.write(8, (CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT >>> 8));
783         bos.write(8, (CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT & 0xff));
784         bos.write(8, BearerData.LANGUAGE_ENGLISH);
785         bos.write(8, 0);  // max messages
786         bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
787 
788         bos.write(8, category1NameLength);
789         for (int i = 0; i < category1NameLength; i++) {
790             bos.write(7, CAT_SEVERE_THREAT.charAt(i));
791         }
792 
793         bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
794         bos.write(8, (CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY >>> 8));
795         bos.write(8, (CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY & 0xff));
796         bos.write(8, BearerData.LANGUAGE_ENGLISH);
797         bos.write(8, 0);  // max messages
798         bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
799 
800         bos.write(8, category2NameLength);
801         for (int i = 0; i < category2NameLength; i++) {
802             bos.write(7, CAT_AMBER_ALERTS.charAt(i));
803         }
804 
805         bos.write(subparamPadBits, 0);
806 
807         SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, bos.toByteArray());
808         assertNotNull(msg);
809         msg.parseSms();
810         List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
811         assertNotNull(programDataList);
812         assertEquals(2, programDataList.size());
813 
814         CdmaSmsCbProgramData programData = programDataList.get(0);
815         assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
816         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT,
817                 programData.getCategory());
818         assertEquals(CAT_SEVERE_THREAT, programData.getCategoryName());
819         assertEquals(BearerData.LANGUAGE_ENGLISH, programData.getLanguage());
820         assertEquals(0, programData.getMaxMessages());
821         assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
822 
823         programData = programDataList.get(1);
824         assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
825         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
826                 programData.getCategory());
827         assertEquals(CAT_AMBER_ALERTS, programData.getCategoryName());
828         assertEquals(BearerData.LANGUAGE_ENGLISH, programData.getLanguage());
829         assertEquals(0, programData.getMaxMessages());
830         assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
831     }
832 
833     private static final byte[] CMAS_TEST_BEARER_DATA = {
834             0x00, 0x03, 0x1C, 0x78, 0x00, 0x01, 0x59, 0x02, (byte) 0xB8, 0x00, 0x02, 0x10,
835             (byte) 0xAA,
836             0x68, (byte) 0xD3, (byte) 0xCD, 0x06, (byte) 0x9E, 0x68, 0x30, (byte) 0xA0, (byte) 0xE9,
837             (byte) 0x97, (byte) 0x9F, 0x44, 0x1B, (byte) 0xF3, 0x20, (byte) 0xE9, (byte) 0xA3,
838             0x2A, 0x08, 0x7B, (byte) 0xF6, (byte) 0xED, (byte) 0xCB, (byte) 0xCB, 0x1E, (byte) 0x9C,
839             0x3B, 0x10, 0x4D, (byte) 0xDF, (byte) 0x8B, 0x4E,
840             (byte) 0xCC, (byte) 0xA8, 0x20, (byte) 0xEC, (byte) 0xCB, (byte) 0xCB, (byte) 0xA2,
841             0x0A,
842             0x7E, 0x79, (byte) 0xF4, (byte) 0xCB, (byte) 0xB5, 0x72, 0x0A, (byte) 0x9A, 0x34,
843             (byte) 0xF3, 0x41, (byte) 0xA7, (byte) 0x9A, 0x0D, (byte) 0xFB, (byte) 0xB6, 0x79, 0x41,
844             (byte) 0x85, 0x07, 0x4C, (byte) 0xBC, (byte) 0xFA, 0x2E, 0x00, 0x08, 0x20, 0x58, 0x38,
845             (byte) 0x88, (byte) 0x80, 0x10, 0x54, 0x06, 0x38, 0x20, 0x60,
846             0x30, (byte) 0xA8, (byte) 0x81, (byte) 0x90, 0x20, 0x08
847     };
848 
849     // Test case for CMAS test message received on the Sprint network.
850     @Test
testDecodeRawBearerData()851     public void testDecodeRawBearerData() {
852         CdmaSmsMessage cdmaSmsMessage =
853                 createBroadcastParcel(CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE);
854         SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, CMAS_TEST_BEARER_DATA);
855 
856         SmsCbMessage cbMessage =
857                 DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
858                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
859         //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
860         assertNotNull("expected non-null for bearer data", cbMessage);
861         assertEquals("geoScope", cbMessage.getGeographicalScope(), 1);
862         assertEquals("serialNumber", cbMessage.getSerialNumber(), 51072);
863         assertEquals("serviceCategory", cbMessage.getServiceCategory(),
864                 CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE);
865         assertEquals("payload", cbMessage.getMessageBody(),
866                 "This is a test of the Commercial Mobile Alert System. This is only a test.");
867 
868         SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
869         assertNotNull("expected non-null for CMAS info", cmasInfo);
870         assertEquals("category", cmasInfo.getCategory(), SmsCbCmasInfo.CMAS_CATEGORY_OTHER);
871         assertEquals("responseType", cmasInfo.getResponseType(),
872                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE);
873         assertEquals("severity", cmasInfo.getSeverity(), SmsCbCmasInfo.CMAS_SEVERITY_SEVERE);
874         assertEquals("urgency", cmasInfo.getUrgency(), SmsCbCmasInfo.CMAS_URGENCY_EXPECTED);
875         assertEquals("certainty", cmasInfo.getCertainty(), SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
876     }
877 }
878