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