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