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