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