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.support.test.filters.FlakyTest;
21import android.telephony.Rlog;
22import android.telephony.SmsCbCmasInfo;
23import android.telephony.SmsCbMessage;
24import android.telephony.cdma.CdmaSmsCbProgramData;
25import android.test.AndroidTestCase;
26import android.test.suitebuilder.annotation.SmallTest;
27
28import com.android.internal.telephony.GsmAlphabet;
29import com.android.internal.telephony.cdma.sms.BearerData;
30import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
31import com.android.internal.telephony.cdma.sms.SmsEnvelope;
32import com.android.internal.telephony.cdma.sms.UserData;
33import com.android.internal.util.BitwiseOutputStream;
34
35import org.junit.Ignore;
36import org.junit.Test;
37
38import java.util.Arrays;
39import java.util.List;
40import java.util.Random;
41
42/**
43 * Test cases for basic SmsCbMessage operation for CDMA.
44 */
45public 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