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