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