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