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