Home | History | Annotate | Download | only in utility
      1 /*
      2  * Copyright (C) 2010 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.exchange.utility;
     18 
     19 import com.android.email.R;
     20 import com.android.email.Utility;
     21 import com.android.email.mail.Address;
     22 import com.android.email.provider.EmailContent.Account;
     23 import com.android.email.provider.EmailContent.Attachment;
     24 import com.android.email.provider.EmailContent.Message;
     25 
     26 import android.content.ContentValues;
     27 import android.content.Entity;
     28 import android.content.res.Resources;
     29 import android.provider.Calendar.Attendees;
     30 import android.provider.Calendar.Events;
     31 import android.test.AndroidTestCase;
     32 import android.util.Log;
     33 
     34 import java.io.BufferedReader;
     35 import java.io.IOException;
     36 import java.io.StringReader;
     37 import java.text.DateFormat;
     38 import java.util.ArrayList;
     39 import java.util.Calendar;
     40 import java.util.Date;
     41 import java.util.GregorianCalendar;
     42 import java.util.HashMap;
     43 import java.util.TimeZone;
     44 
     45 /**
     46  * Tests of EAS Calendar Utilities
     47  * You can run this entire test case with:
     48  *   runtest -c com.android.exchange.utility.CalendarUtilitiesTests email
     49  *
     50  * Please see RFC2445 for RRULE definition
     51  * http://www.ietf.org/rfc/rfc2445.txt
     52  */
     53 
     54 public class CalendarUtilitiesTests extends AndroidTestCase {
     55 
     56     // Some prebuilt time zones, Base64 encoded (as they arrive from EAS)
     57     // More time zones to be added over time
     58 
     59     // Not all time zones are appropriate for testing.  For example, ISRAEL_STANDARD_TIME cannot be
     60     // used because DST is determined from year to year in a non-standard way (related to the lunar
     61     // calendar); therefore, the test would only work during the year in which it was created
     62 
     63     // This time zone has no DST
     64     private static final String ASIA_CALCUTTA_TIME =
     65         "tv7//0kAbgBkAGkAYQAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
     66         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEkAbgBkAGkAYQAgAEQAYQB5AGwAaQBnAGgAdAAgAFQAaQBtAGUA" +
     67         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
     68 
     69     // This time zone is equivalent to PST and uses DST
     70     private static final String AMERICA_DAWSON_TIME =
     71         "4AEAAFAAYQBjAGkAZgBpAGMAIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAA" +
     72         "AAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAFAAYQBjAGkAZgBpAGMAIABEAGEAeQBsAGkAZwBoAHQAIABUAGkA" +
     73         "bQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==";
     74 
     75     // Test a southern hemisphere time zone w/ DST
     76     private static final String AUSTRALIA_ACT_TIME =
     77         "qP3//0EAVQBTACAARQBhAHMAdABlAHIAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAA" +
     78         "AAAAAAAAAAQAAAABAAMAAAAAAAAAAAAAAEEAVQBTACAARQBhAHMAdABlAHIAbgAgAEQAYQB5AGwAaQBnAGgA" +
     79         "dAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAoAAAABAAIAAAAAAAAAxP///w==";
     80 
     81     // Test a european time zone w/ DST
     82     private static final String EUROPE_MOSCOW_TIME =
     83         "TP///1IAdQBzAHMAaQBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAA" +
     84         "AAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAFIAdQBzAHMAaQBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkA" +
     85         "bQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w==";
     86 
     87     // Test a timezone with GMT bias but bogus DST parameters (there is no equivalent time zone
     88     // in the database)
     89     private static final String GMT_UNKNOWN_DAYLIGHT_TIME =
     90         "AAAAACgARwBNAFQAKwAwADAAOgAwADAAKQAgAFQAaQBtAGUAIABaAG8AbgBlAAAAAAAAAAAAAAAAAAAAAAAA" +
     91         "AAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAACgARwBNAFQAKwAwADAAOgAwADAAKQAgAFQAaQBtAGUAIABaAG8A" +
     92         "bgBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAEAAAAAAAAAxP///w==";
     93 
     94     // This time zone has no DST, but earlier, buggy code retrieved a TZ WITH DST
     95     private static final String ARIZONA_TIME =
     96         "pAEAAFUAUwAgAE0AbwB1AG4AdABhAGkAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAA" +
     97         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUAUwAgAE0AbwB1AG4AdABhAGkAbgAgAEQAYQB5AGwAaQBnAGgA" +
     98         "dAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
     99 
    100     private static final String ORGANIZER = "organizer (at) server.com";
    101     private static final String ATTENDEE = "attendee (at) server.com";
    102 
    103     public void testGetSet() {
    104         byte[] bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
    105 
    106         // First, check that getWord/Long are properly little endian
    107         assertEquals(0x0100, CalendarUtilities.getWord(bytes, 0));
    108         assertEquals(0x03020100, CalendarUtilities.getLong(bytes, 0));
    109         assertEquals(0x07060504, CalendarUtilities.getLong(bytes, 4));
    110 
    111         // Set some words and longs
    112         CalendarUtilities.setWord(bytes, 0, 0xDEAD);
    113         CalendarUtilities.setLong(bytes, 2, 0xBEEFBEEF);
    114         CalendarUtilities.setWord(bytes, 6, 0xCEDE);
    115 
    116         // Retrieve them
    117         assertEquals(0xDEAD, CalendarUtilities.getWord(bytes, 0));
    118         assertEquals(0xBEEFBEEF, CalendarUtilities.getLong(bytes, 2));
    119         assertEquals(0xCEDE, CalendarUtilities.getWord(bytes, 6));
    120     }
    121 
    122     public void testParseTimeZoneEndToEnd() {
    123         TimeZone tz = CalendarUtilities.tziStringToTimeZone(AMERICA_DAWSON_TIME);
    124         assertEquals("America/Dawson", tz.getID());
    125         tz = CalendarUtilities.tziStringToTimeZone(ASIA_CALCUTTA_TIME);
    126         assertEquals("Asia/Calcutta", tz.getID());
    127         tz = CalendarUtilities.tziStringToTimeZone(AUSTRALIA_ACT_TIME);
    128         assertEquals("Australia/ACT", tz.getID());
    129         tz = CalendarUtilities.tziStringToTimeZone(EUROPE_MOSCOW_TIME);
    130         assertEquals("Europe/Moscow", tz.getID());
    131         tz = CalendarUtilities.tziStringToTimeZone(GMT_UNKNOWN_DAYLIGHT_TIME);
    132         int bias = tz.getOffset(System.currentTimeMillis());
    133         assertEquals(0, bias);
    134         // Make sure non-DST TZ's work properly
    135         tz = CalendarUtilities.tziStringToTimeZone(ARIZONA_TIME);
    136         assertEquals("America/Phoenix", tz.getID());
    137     }
    138 
    139     public void testGenerateEasDayOfWeek() {
    140         String byDay = "TU,WE,SA";
    141         // TU = 4, WE = 8; SA = 64;
    142         assertEquals("76", CalendarUtilities.generateEasDayOfWeek(byDay));
    143         // MO = 2, TU = 4; WE = 8; TH = 16; FR = 32
    144         byDay = "MO,TU,WE,TH,FR";
    145         assertEquals("62", CalendarUtilities.generateEasDayOfWeek(byDay));
    146         // SU = 1
    147         byDay = "SU";
    148         assertEquals("1", CalendarUtilities.generateEasDayOfWeek(byDay));
    149     }
    150 
    151     public void testTokenFromRrule() {
    152         String rrule = "FREQ=DAILY;INTERVAL=1;BYDAY=WE,TH,SA;BYMONTHDAY=17";
    153         assertEquals("DAILY", CalendarUtilities.tokenFromRrule(rrule, "FREQ="));
    154         assertEquals("1", CalendarUtilities.tokenFromRrule(rrule, "INTERVAL="));
    155         assertEquals("17", CalendarUtilities.tokenFromRrule(rrule, "BYMONTHDAY="));
    156         assertEquals("WE,TH,SA", CalendarUtilities.tokenFromRrule(rrule, "BYDAY="));
    157         assertNull(CalendarUtilities.tokenFromRrule(rrule, "UNTIL="));
    158     }
    159 
    160     public void testRecurrenceUntilToEasUntil() {
    161         // Test full format
    162         assertEquals("YYYYMMDDT000000Z",
    163                 CalendarUtilities.recurrenceUntilToEasUntil("YYYYMMDDTHHMMSSZ"));
    164         // Test date only format
    165         assertEquals("YYYYMMDDT000000Z",
    166                 CalendarUtilities.recurrenceUntilToEasUntil("YYYYMMDD"));
    167     }
    168 
    169     public void testParseEmailDateTimeToMillis(String date) {
    170         // Format for email date strings is 2010-02-23T16:00:00.000Z
    171         String dateString = "2010-02-23T15:16:17.000Z";
    172         long dateTime = Utility.parseEmailDateTimeToMillis(dateString);
    173         GregorianCalendar cal = new GregorianCalendar();
    174         cal.setTimeInMillis(dateTime);
    175         cal.setTimeZone(TimeZone.getTimeZone("GMT"));
    176         assertEquals(cal.get(Calendar.YEAR), 2010);
    177         assertEquals(cal.get(Calendar.MONTH), 1);  // 0 based
    178         assertEquals(cal.get(Calendar.DAY_OF_MONTH), 23);
    179         assertEquals(cal.get(Calendar.HOUR_OF_DAY), 16);
    180         assertEquals(cal.get(Calendar.MINUTE), 16);
    181         assertEquals(cal.get(Calendar.SECOND), 17);
    182     }
    183 
    184     public void testParseDateTimeToMillis(String date) {
    185         // Format for calendar date strings is 20100223T160000000Z
    186         String dateString = "20100223T151617000Z";
    187         long dateTime = Utility.parseDateTimeToMillis(dateString);
    188         GregorianCalendar cal = new GregorianCalendar();
    189         cal.setTimeInMillis(dateTime);
    190         cal.setTimeZone(TimeZone.getTimeZone("GMT"));
    191         assertEquals(cal.get(Calendar.YEAR), 2010);
    192         assertEquals(cal.get(Calendar.MONTH), 1);  // 0 based
    193         assertEquals(cal.get(Calendar.DAY_OF_MONTH), 23);
    194         assertEquals(cal.get(Calendar.HOUR_OF_DAY), 16);
    195         assertEquals(cal.get(Calendar.MINUTE), 16);
    196         assertEquals(cal.get(Calendar.SECOND), 17);
    197     }
    198 
    199     private Entity setupTestEventEntity(String organizer, String attendee, String title) {
    200         // Create an Entity for an Event
    201         ContentValues entityValues = new ContentValues();
    202         Entity entity = new Entity(entityValues);
    203 
    204         // Set up values for the Event
    205         String location = "Meeting Location";
    206 
    207         // Fill in times, location, title, and organizer
    208         entityValues.put("DTSTAMP",
    209                 CalendarUtilities.convertEmailDateTimeToCalendarDateTime("2010-04-05T14:30:51Z"));
    210         entityValues.put(Events.DTSTART,
    211                 Utility.parseEmailDateTimeToMillis("2010-04-12T18:30:00Z"));
    212         entityValues.put(Events.DTEND,
    213                 Utility.parseEmailDateTimeToMillis("2010-04-12T19:30:00Z"));
    214         entityValues.put(Events.EVENT_LOCATION, location);
    215         entityValues.put(Events.TITLE, title);
    216         entityValues.put(Events.ORGANIZER, organizer);
    217         entityValues.put(Events._SYNC_DATA, "31415926535");
    218 
    219         // Add the attendee
    220         ContentValues attendeeValues = new ContentValues();
    221         attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
    222         attendeeValues.put(Attendees.ATTENDEE_EMAIL, attendee);
    223         entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
    224 
    225         // Add the organizer
    226         ContentValues organizerValues = new ContentValues();
    227         organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
    228         organizerValues.put(Attendees.ATTENDEE_EMAIL, organizer);
    229         entity.addSubValue(Attendees.CONTENT_URI, organizerValues);
    230         return entity;
    231     }
    232 
    233     private Entity setupTestExceptionEntity(String organizer, String attendee, String title) {
    234         Entity entity = setupTestEventEntity(organizer, attendee, title);
    235         ContentValues entityValues = entity.getEntityValues();
    236         entityValues.put(Events.ORIGINAL_EVENT, 69);
    237         // The exception will be on April 26th
    238         entityValues.put(Events.ORIGINAL_INSTANCE_TIME,
    239                 Utility.parseEmailDateTimeToMillis("2010-04-26T18:30:00Z"));
    240         return entity;
    241     }
    242 
    243     public void testCreateMessageForEntity_Reply() {
    244         // Set up the "event"
    245         String title = "Discuss Unit Tests";
    246         Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
    247 
    248         // Create a dummy account for the attendee
    249         Account account = new Account();
    250         account.mEmailAddress = ATTENDEE;
    251 
    252         // The uid is required, but can be anything
    253         String uid = "31415926535";
    254 
    255         // Create the outgoing message
    256         Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
    257                 Message.FLAG_OUTGOING_MEETING_ACCEPT, uid, account);
    258 
    259         // First, we should have a message
    260         assertNotNull(msg);
    261 
    262         // Now check some of the fields of the message
    263         assertEquals(Address.pack(new Address[] {new Address(ORGANIZER)}), msg.mTo);
    264         Resources resources = getContext().getResources();
    265         String accept = resources.getString(R.string.meeting_accepted, title);
    266         assertEquals(accept, msg.mSubject);
    267         assertNotNull(msg.mText);
    268         assertTrue(msg.mText.contains(resources.getString(R.string.meeting_where, "")));
    269 
    270         // And make sure we have an attachment
    271         assertNotNull(msg.mAttachments);
    272         assertEquals(1, msg.mAttachments.size());
    273         Attachment att = msg.mAttachments.get(0);
    274         // And that the attachment has the correct elements
    275         assertEquals("invite.ics", att.mFileName);
    276         assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
    277                 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
    278         assertEquals("text/calendar; method=REPLY", att.mMimeType);
    279         assertNotNull(att.mContentBytes);
    280         assertEquals(att.mSize, att.mContentBytes.length);
    281 
    282         //TODO Check the contents of the attachment using an iCalendar parser
    283     }
    284 
    285     public void testCreateMessageForEntity_Invite_AllDay() throws IOException {
    286         // Set up the "event"
    287         String title = "Discuss Unit Tests";
    288         Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
    289         entity.getEntityValues().put(Events.ALL_DAY, 1);
    290 
    291         // Create a dummy account for the attendee
    292         Account account = new Account();
    293         account.mEmailAddress = ORGANIZER;
    294 
    295         // The uid is required, but can be anything
    296         String uid = "31415926535";
    297 
    298         // Create the outgoing message
    299         Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
    300                 Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
    301 
    302         // First, we should have a message
    303         assertNotNull(msg);
    304 
    305         // Now check some of the fields of the message
    306         assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
    307         assertEquals(title, msg.mSubject);
    308 
    309         // And make sure we have an attachment
    310         assertNotNull(msg.mAttachments);
    311         assertEquals(1, msg.mAttachments.size());
    312         Attachment att = msg.mAttachments.get(0);
    313         // And that the attachment has the correct elements
    314         assertEquals("invite.ics", att.mFileName);
    315         assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
    316                 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
    317         assertEquals("text/calendar; method=REQUEST", att.mMimeType);
    318         assertNotNull(att.mContentBytes);
    319         assertEquals(att.mSize, att.mContentBytes.length);
    320 
    321         // We'll check the contents of the ics file here
    322         BlockHash vcalendar = parseIcsContent(att.mContentBytes);
    323         assertNotNull(vcalendar);
    324 
    325         // We should have a VCALENDAR with a REQUEST method
    326         assertEquals("VCALENDAR", vcalendar.name);
    327         assertEquals("REQUEST", vcalendar.get("METHOD"));
    328 
    329         // We should have one block under VCALENDAR
    330         assertEquals(1, vcalendar.blocks.size());
    331         BlockHash vevent = vcalendar.blocks.get(0);
    332         // It's a VEVENT with the following fields
    333         assertEquals("VEVENT", vevent.name);
    334         assertEquals("Meeting Location", vevent.get("LOCATION"));
    335         assertEquals("0", vevent.get("SEQUENCE"));
    336         assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
    337         assertEquals(uid, vevent.get("UID"));
    338         assertEquals("MAILTO:" + ATTENDEE,
    339                 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
    340 
    341         // These next two fields should have a date only
    342         assertEquals("20100412", vevent.get("DTSTART;VALUE=DATE"));
    343         assertEquals("20100412", vevent.get("DTEND;VALUE=DATE"));
    344         // This should be set to TRUE for all-day events
    345         assertEquals("TRUE", vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
    346     }
    347 
    348     public void testCreateMessageForEntity_Invite() throws IOException {
    349         // Set up the "event"
    350         String title = "Discuss Unit Tests";
    351         Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
    352 
    353         // Create a dummy account for the attendee
    354         Account account = new Account();
    355         account.mEmailAddress = ORGANIZER;
    356 
    357         // The uid is required, but can be anything
    358         String uid = "31415926535";
    359 
    360         // Create the outgoing message
    361         Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
    362                 Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
    363 
    364         // First, we should have a message
    365         assertNotNull(msg);
    366 
    367         // Now check some of the fields of the message
    368         assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
    369         assertEquals(title, msg.mSubject);
    370 
    371         // And make sure we have an attachment
    372         assertNotNull(msg.mAttachments);
    373         assertEquals(1, msg.mAttachments.size());
    374         Attachment att = msg.mAttachments.get(0);
    375         // And that the attachment has the correct elements
    376         assertEquals("invite.ics", att.mFileName);
    377         assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
    378                 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
    379         assertEquals("text/calendar; method=REQUEST", att.mMimeType);
    380         assertNotNull(att.mContentBytes);
    381         assertEquals(att.mSize, att.mContentBytes.length);
    382 
    383         // We'll check the contents of the ics file here
    384         BlockHash vcalendar = parseIcsContent(att.mContentBytes);
    385         assertNotNull(vcalendar);
    386 
    387         // We should have a VCALENDAR with a REQUEST method
    388         assertEquals("VCALENDAR", vcalendar.name);
    389         assertEquals("REQUEST", vcalendar.get("METHOD"));
    390 
    391         // We should have one block under VCALENDAR
    392         assertEquals(1, vcalendar.blocks.size());
    393         BlockHash vevent = vcalendar.blocks.get(0);
    394         // It's a VEVENT with the following fields
    395         assertEquals("VEVENT", vevent.name);
    396         assertEquals("Meeting Location", vevent.get("LOCATION"));
    397         assertEquals("0", vevent.get("SEQUENCE"));
    398         assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
    399         assertEquals(uid, vevent.get("UID"));
    400         assertEquals("MAILTO:" + ATTENDEE,
    401                 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
    402 
    403         // These next two fields should exist (without the VALUE=DATE suffix)
    404         assertNotNull(vevent.get("DTSTART"));
    405         assertNotNull(vevent.get("DTEND"));
    406         assertNull(vevent.get("DTSTART;VALUE=DATE"));
    407         assertNull(vevent.get("DTEND;VALUE=DATE"));
    408         // This shouldn't exist for this event
    409         assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
    410     }
    411 
    412     public void testCreateMessageForEntity_Recurring() throws IOException {
    413         // Set up the "event"
    414         String title = "Discuss Unit Tests";
    415         Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
    416         // Set up a RRULE for this event
    417         entity.getEntityValues().put(Events.RRULE, "FREQ=DAILY");
    418 
    419         // Create a dummy account for the attendee
    420         Account account = new Account();
    421         account.mEmailAddress = ORGANIZER;
    422 
    423         // The uid is required, but can be anything
    424         String uid = "31415926535";
    425 
    426         // Create the outgoing message
    427         Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
    428                 Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
    429 
    430         // First, we should have a message
    431         assertNotNull(msg);
    432 
    433         // Now check some of the fields of the message
    434         assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
    435         assertEquals(title, msg.mSubject);
    436 
    437         // And make sure we have an attachment
    438         assertNotNull(msg.mAttachments);
    439         assertEquals(1, msg.mAttachments.size());
    440         Attachment att = msg.mAttachments.get(0);
    441         // And that the attachment has the correct elements
    442         assertEquals("invite.ics", att.mFileName);
    443         assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
    444                 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
    445         assertEquals("text/calendar; method=REQUEST", att.mMimeType);
    446         assertNotNull(att.mContentBytes);
    447         assertEquals(att.mSize, att.mContentBytes.length);
    448 
    449         // We'll check the contents of the ics file here
    450         BlockHash vcalendar = parseIcsContent(att.mContentBytes);
    451         assertNotNull(vcalendar);
    452 
    453         // We should have a VCALENDAR with a REQUEST method
    454         assertEquals("VCALENDAR", vcalendar.name);
    455         assertEquals("REQUEST", vcalendar.get("METHOD"));
    456 
    457         // We should have two blocks under VCALENDAR (VTIMEZONE and VEVENT)
    458         assertEquals(2, vcalendar.blocks.size());
    459 
    460         // This is the time zone that should be used
    461         TimeZone timeZone = TimeZone.getDefault();
    462 
    463         BlockHash vtimezone = vcalendar.blocks.get(0);
    464         // It should be a VTIMEZONE for timeZone
    465         assertEquals("VTIMEZONE", vtimezone.name);
    466         assertEquals(timeZone.getID(), vtimezone.get("TZID"));
    467 
    468         BlockHash vevent = vcalendar.blocks.get(1);
    469         // It's a VEVENT with the following fields
    470         assertEquals("VEVENT", vevent.name);
    471         assertEquals("Meeting Location", vevent.get("LOCATION"));
    472         assertEquals("0", vevent.get("SEQUENCE"));
    473         assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
    474         assertEquals(uid, vevent.get("UID"));
    475         assertEquals("MAILTO:" + ATTENDEE,
    476                 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
    477 
    478         // We should have DTSTART/DTEND with time zone
    479         assertNotNull(vevent.get("DTSTART;TZID=" + timeZone.getID()));
    480         assertNotNull(vevent.get("DTEND;TZID=" + timeZone.getID()));
    481         assertNull(vevent.get("DTSTART"));
    482         assertNull(vevent.get("DTEND"));
    483         assertNull(vevent.get("DTSTART;VALUE=DATE"));
    484         assertNull(vevent.get("DTEND;VALUE=DATE"));
    485         // This shouldn't exist for this event
    486         assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
    487     }
    488 
    489     public void testCreateMessageForEntity_Exception_Cancel() throws IOException {
    490         // Set up the "exception"...
    491         String title = "Discuss Unit Tests";
    492         Entity entity = setupTestExceptionEntity(ORGANIZER, ATTENDEE, title);
    493 
    494         ContentValues entityValues = entity.getEntityValues();
    495         // Mark the Exception as dirty
    496         entityValues.put(Events._SYNC_DIRTY, 1);
    497         // And mark it canceled
    498         entityValues.put(Events.STATUS, Events.STATUS_CANCELED);
    499 
    500         // Create a dummy account for the attendee
    501         Account account = new Account();
    502         account.mEmailAddress = ORGANIZER;
    503 
    504         // The uid is required, but can be anything
    505         String uid = "31415926535";
    506 
    507         // Create the outgoing message
    508         Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
    509                 Message.FLAG_OUTGOING_MEETING_CANCEL, uid, account);
    510 
    511         // First, we should have a message
    512         assertNotNull(msg);
    513 
    514         // Now check some of the fields of the message
    515         assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
    516         String cancel = getContext().getResources().getString(R.string.meeting_canceled, title);
    517         assertEquals(cancel, msg.mSubject);
    518 
    519         // And make sure we have an attachment
    520         assertNotNull(msg.mAttachments);
    521         assertEquals(1, msg.mAttachments.size());
    522         Attachment att = msg.mAttachments.get(0);
    523         // And that the attachment has the correct elements
    524         assertEquals("invite.ics", att.mFileName);
    525         assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
    526                 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
    527         assertEquals("text/calendar; method=CANCEL", att.mMimeType);
    528         assertNotNull(att.mContentBytes);
    529 
    530         // We'll check the contents of the ics file here
    531         BlockHash vcalendar = parseIcsContent(att.mContentBytes);
    532         assertNotNull(vcalendar);
    533 
    534         // We should have a VCALENDAR with a CANCEL method
    535         assertEquals("VCALENDAR", vcalendar.name);
    536         assertEquals("CANCEL", vcalendar.get("METHOD"));
    537 
    538         // This is the time zone that should be used
    539         TimeZone timeZone = TimeZone.getDefault();
    540 
    541         // We should have two blocks under VCALENDAR (VTIMEZONE and VEVENT)
    542         assertEquals(2, vcalendar.blocks.size());
    543 
    544         BlockHash vtimezone = vcalendar.blocks.get(0);
    545         // It should be a VTIMEZONE for timeZone
    546         assertEquals("VTIMEZONE", vtimezone.name);
    547         assertEquals(timeZone.getID(), vtimezone.get("TZID"));
    548 
    549         BlockHash vevent = vcalendar.blocks.get(1);
    550         // It's a VEVENT with the following fields
    551         assertEquals("VEVENT", vevent.name);
    552         assertEquals("Meeting Location", vevent.get("LOCATION"));
    553         assertEquals("0", vevent.get("SEQUENCE"));
    554         assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
    555         assertEquals(uid, vevent.get("UID"));
    556         assertEquals("MAILTO:" + ATTENDEE,
    557                 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT"));
    558         long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
    559         assertNotSame(0, originalTime);
    560         // For an exception, RECURRENCE-ID is critical
    561         assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone,
    562                 true /*withTime*/), vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID()));
    563     }
    564 
    565     public void testUtcOffsetString() {
    566         assertEquals(CalendarUtilities.utcOffsetString(540), "+0900");
    567         assertEquals(CalendarUtilities.utcOffsetString(-480), "-0800");
    568         assertEquals(CalendarUtilities.utcOffsetString(0), "+0000");
    569     }
    570 
    571     public void testFindTransitionDate() {
    572         // We'll find some transitions and make sure that we're properly in or out of daylight time
    573         // on either side of the transition.
    574         // Use CST for testing (any other will do as well, as long as it has DST)
    575         TimeZone tz = TimeZone.getTimeZone("US/Central");
    576         // Get a calendar at January 1st of the current year
    577         GregorianCalendar calendar = new GregorianCalendar(tz);
    578         calendar.set(CalendarUtilities.sCurrentYear, Calendar.JANUARY, 1);
    579         // Get start and end times at start and end of year
    580         long startTime = calendar.getTimeInMillis();
    581         long endTime = startTime + (365*CalendarUtilities.DAYS);
    582         // Find the first transition
    583         GregorianCalendar transitionCalendar =
    584             CalendarUtilities.findTransitionDate(tz, startTime, endTime, false);
    585         long transitionTime = transitionCalendar.getTimeInMillis();
    586         // Before should be in standard time; after in daylight time
    587         Date beforeDate = new Date(transitionTime - CalendarUtilities.HOURS);
    588         Date afterDate = new Date(transitionTime + CalendarUtilities.HOURS);
    589         assertFalse(tz.inDaylightTime(beforeDate));
    590         assertTrue(tz.inDaylightTime(afterDate));
    591 
    592         // Find the next one...
    593         transitionCalendar = CalendarUtilities.findTransitionDate(tz, transitionTime +
    594                 CalendarUtilities.DAYS, endTime, true);
    595         transitionTime = transitionCalendar.getTimeInMillis();
    596         // This time, Before should be in daylight time; after in standard time
    597         beforeDate = new Date(transitionTime - CalendarUtilities.HOURS);
    598         afterDate = new Date(transitionTime + CalendarUtilities.HOURS);
    599         assertTrue(tz.inDaylightTime(beforeDate));
    600         assertFalse(tz.inDaylightTime(afterDate));
    601 
    602         // Captain Renault: What in heaven's name brought you to Casablanca?
    603         // Rick: My health. I came to Casablanca for the waters.
    604         // Also, they have no daylight savings time
    605         tz = TimeZone.getTimeZone("Africa/Casablanca");
    606         // Get a calendar at January 1st of the current year
    607         calendar = new GregorianCalendar(tz);
    608         calendar.set(CalendarUtilities.sCurrentYear, Calendar.JANUARY, 1);
    609         // Get start and end times at start and end of year
    610         startTime = calendar.getTimeInMillis();
    611         endTime = startTime + (365*CalendarUtilities.DAYS);
    612         // Find the first transition
    613         transitionCalendar = CalendarUtilities.findTransitionDate(tz, startTime, endTime, false);
    614         // There had better not be one
    615         assertNull(transitionCalendar);
    616     }
    617 
    618     public void testRruleFromRecurrence() {
    619         // Every Monday for 2 weeks
    620         String rrule = CalendarUtilities.rruleFromRecurrence(
    621                 1 /*Weekly*/, 2 /*Occurrences*/, 1 /*Interval*/, 2 /*Monday*/, 0, 0, 0, null);
    622         assertEquals("FREQ=WEEKLY;INTERVAL=1;COUNT=2;BYDAY=MO", rrule);
    623         // Every Tuesday and Friday
    624         rrule = CalendarUtilities.rruleFromRecurrence(
    625                 1 /*Weekly*/, 0 /*Occurrences*/, 0 /*Interval*/, 36 /*Tue&Fri*/, 0, 0, 0, null);
    626         assertEquals("FREQ=WEEKLY;BYDAY=TU,FR", rrule);
    627         // The last Saturday of the month
    628         rrule = CalendarUtilities.rruleFromRecurrence(
    629                 3 /*Monthly/DayofWeek*/, 0, 0, 64 /*Sat*/, 0, 5 /*Last*/, 0, null);
    630         assertEquals("FREQ=MONTHLY;BYDAY=-1SA", rrule);
    631         // The third Wednesday and Thursday of the month
    632         rrule = CalendarUtilities.rruleFromRecurrence(
    633                 3 /*Monthly/DayofWeek*/, 0, 0, 24 /*Wed&Thu*/, 0, 3 /*3rd*/, 0, null);
    634         assertEquals("FREQ=MONTHLY;BYDAY=3WE,3TH", rrule);
    635         // The 14th of the every month
    636         rrule = CalendarUtilities.rruleFromRecurrence(
    637                 2 /*Monthly/Date*/, 0, 0, 0, 14 /*14th*/, 0, 0, null);
    638         assertEquals("FREQ=MONTHLY;BYMONTHDAY=14", rrule);
    639         // Every 31st of October
    640         rrule = CalendarUtilities.rruleFromRecurrence(
    641                 5 /*Yearly/Date*/, 0, 0, 0, 31 /*31st*/, 0, 10 /*October*/, null);
    642         assertEquals("FREQ=YEARLY;BYMONTHDAY=31;BYMONTH=10", rrule);
    643         // The first Tuesday of June
    644         rrule = CalendarUtilities.rruleFromRecurrence(
    645                 6 /*Yearly/Month/DayOfWeek*/, 0, 0, 4 /*Tue*/, 0, 1 /*1st*/, 6 /*June*/, null);
    646         assertEquals("FREQ=YEARLY;BYDAY=1TU;BYMONTH=6", rrule);
    647     }
    648 
    649     /**
    650      * For debugging purposes, to help keep track of parsing errors.
    651      */
    652     private class UnterminatedBlockException extends IOException {
    653         private static final long serialVersionUID = 1L;
    654         UnterminatedBlockException(String name) {
    655             super(name);
    656         }
    657     }
    658 
    659     /**
    660      * A lightweight representation of block object containing a hash of individual values and an
    661      * array of inner blocks.  The object is build by pulling elements from a BufferedReader.
    662      * NOTE: Multiple values of a given field are not supported.  We'd see this with ATTENDEEs, for
    663      * example, and possibly RDATEs in VTIMEZONEs without an RRULE; these cases will be handled
    664      * at a later time.
    665      */
    666     private class BlockHash {
    667         String name;
    668         HashMap<String, String> hash = new HashMap<String, String>();
    669         ArrayList<BlockHash> blocks = new ArrayList<BlockHash>();
    670 
    671         BlockHash (String _name, BufferedReader reader) throws IOException {
    672             name = _name;
    673             String lastField = null;
    674             String lastValue = null;
    675             while (true) {
    676                 // Get a line; we're done if it's null
    677                 String line = reader.readLine();
    678                 if (line == null) {
    679                     throw new UnterminatedBlockException(name);
    680                 }
    681                 int length = line.length();
    682                 if (length == 0) {
    683                     // We shouldn't ever see an empty line
    684                     throw new IllegalArgumentException();
    685                 }
    686                 // A line starting with tab is a continuation
    687                 if (line.charAt(0) == '\t') {
    688                     // Remember the line and length
    689                     lastValue = line.substring(1);
    690                     // Save the concatenation of old and new values
    691                     hash.put(lastField, hash.get(lastField) + lastValue);
    692                     continue;
    693                 }
    694                 // Find the field delimiter
    695                 int pos = line.indexOf(':');
    696                 // If not found, or at EOL, this is a bad ics
    697                 if (pos < 0 || pos >= length) {
    698                     throw new IllegalArgumentException();
    699                 }
    700                 // Remember the field, value, and length
    701                 lastField = line.substring(0, pos);
    702                 lastValue = line.substring(pos + 1);
    703                 if (lastField.equals("BEGIN")) {
    704                     blocks.add(new BlockHash(lastValue, reader));
    705                     continue;
    706                 } else if (lastField.equals("END")) {
    707                     if (!lastValue.equals(name)) {
    708                         throw new UnterminatedBlockException(name);
    709                     }
    710                     break;
    711                 }
    712 
    713                 // Save it away and continue
    714                 hash.put(lastField, lastValue);
    715             }
    716         }
    717 
    718         String get(String field) {
    719             return hash.get(field);
    720         }
    721     }
    722 
    723     private BlockHash parseIcsContent(byte[] bytes) throws IOException {
    724         BufferedReader reader = new BufferedReader(new StringReader(Utility.fromUtf8(bytes)));
    725         String line = reader.readLine();
    726         if (!line.equals("BEGIN:VCALENDAR")) {
    727             throw new IllegalArgumentException();
    728         }
    729         return new BlockHash("VCALENDAR", reader);
    730     }
    731 
    732     public void testBuildMessageTextFromEntityValues() {
    733         // Set up a test event
    734         String title = "Event Title";
    735         Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
    736         ContentValues entityValues = entity.getEntityValues();
    737 
    738         // Save this away; we'll use it a few times below
    739         Resources resources = mContext.getResources();
    740         Date date = new Date(entityValues.getAsLong(Events.DTSTART));
    741         String dateTimeString = DateFormat.getDateTimeInstance().format(date);
    742 
    743         // Get the text for this message
    744         StringBuilder sb = new StringBuilder();
    745         CalendarUtilities.buildMessageTextFromEntityValues(mContext, entityValues, sb);
    746         String text = sb.toString();
    747         // We'll just check the when and where
    748         assertTrue(text.contains(resources.getString(R.string.meeting_when, dateTimeString)));
    749         String location = entityValues.getAsString(Events.EVENT_LOCATION);
    750         assertTrue(text.contains(resources.getString(R.string.meeting_where, location)));
    751 
    752         // Make this event recurring
    753         entity.getEntityValues().put(Events.RRULE, "FREQ=WEEKLY;BYDAY=MO");
    754         sb = new StringBuilder();
    755         CalendarUtilities.buildMessageTextFromEntityValues(mContext, entityValues, sb);
    756         text = sb.toString();
    757         assertTrue(text.contains(resources.getString(R.string.meeting_recurring, dateTimeString)));
    758     }
    759 
    760     /**
    761      * Sanity test for time zone generation.  Most important, make sure that we can run through
    762      * all of the time zones without generating an exception.  Second, make sure that we're finding
    763      * rules for at least 90% of time zones that use daylight time (empirically, it's more like
    764      * 95%).  Log those without rules.
    765      * @throws IOException
    766      */
    767     public void testTimeZoneToVTimezone() throws IOException {
    768         SimpleIcsWriter writer = new SimpleIcsWriter();
    769         int rule = 0;
    770         int nodst = 0;
    771         int norule = 0;
    772         ArrayList<String> norulelist = new ArrayList<String>();
    773         for (String tzs: TimeZone.getAvailableIDs()) {
    774             TimeZone tz = TimeZone.getTimeZone(tzs);
    775             writer = new SimpleIcsWriter();
    776             CalendarUtilities.timeZoneToVTimezone(tz, writer);
    777             String vc = writer.toString();
    778             boolean hasRule = vc.indexOf("RRULE") > 0;
    779             if (hasRule) {
    780                 rule++;
    781             } else if (tz.useDaylightTime()) {
    782                 norule++;
    783                 norulelist.add(tz.getID());
    784             } else {
    785                 nodst++;
    786             }
    787         }
    788         assertTrue(norule < rule/10);
    789         Log.d("TimeZoneGeneration",
    790                 "Rule: " + rule + ", No DST: " + nodst + ", No rule: " + norule);
    791         for (String nr: norulelist) {
    792             Log.d("TimeZoneGeneration", "No rule: " + nr);
    793         }
    794     }
    795 
    796     public void testGetUidFromGlobalObjId() {
    797         // This is a "foreign" uid (from some vCalendar client)
    798         String globalObjId = "BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAA" +
    799                 "HZDYWwtVWlkAQAAADI3NjU1NmRkLTg1MzAtNGZiZS1iMzE0LThiM2JlYTYwMjE0OQA=";
    800         String uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId);
    801         assertEquals(uid, "276556dd-8530-4fbe-b314-8b3bea602149");
    802         // This is a native EAS uid
    803         globalObjId =
    804             "BAAAAIIA4AB0xbcQGoLgCAAAAADACTu7KbPKAQAAAAAAAAAAEAAAAObgsG6HVt1Fmy+7GlLbGhY=";
    805         uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId);
    806         assertEquals(uid, "040000008200E00074C5B7101A82E00800000000C0093BBB29B3CA" +
    807                 "01000000000000000010000000E6E0B06E8756DD459B2FBB1A52DB1A16");
    808     }
    809 
    810     public void testSelfAttendeeStatusFromBusyStatus() {
    811         assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED,
    812                 CalendarUtilities.attendeeStatusFromBusyStatus(
    813                         CalendarUtilities.BUSY_STATUS_BUSY));
    814         assertEquals(Attendees.ATTENDEE_STATUS_TENTATIVE,
    815                 CalendarUtilities.attendeeStatusFromBusyStatus(
    816                         CalendarUtilities.BUSY_STATUS_TENTATIVE));
    817         assertEquals(Attendees.ATTENDEE_STATUS_NONE,
    818                 CalendarUtilities.attendeeStatusFromBusyStatus(
    819                         CalendarUtilities.BUSY_STATUS_FREE));
    820         assertEquals(Attendees.ATTENDEE_STATUS_NONE,
    821                 CalendarUtilities.attendeeStatusFromBusyStatus(
    822                         CalendarUtilities.BUSY_STATUS_OUT_OF_OFFICE));
    823     }
    824 
    825     public void testBusyStatusFromSelfStatus() {
    826         assertEquals(CalendarUtilities.BUSY_STATUS_FREE,
    827                 CalendarUtilities.busyStatusFromAttendeeStatus(
    828                         Attendees.ATTENDEE_STATUS_DECLINED));
    829         assertEquals(CalendarUtilities.BUSY_STATUS_FREE,
    830                 CalendarUtilities.busyStatusFromAttendeeStatus(
    831                         Attendees.ATTENDEE_STATUS_NONE));
    832         assertEquals(CalendarUtilities.BUSY_STATUS_FREE,
    833                 CalendarUtilities.busyStatusFromAttendeeStatus(
    834                         Attendees.ATTENDEE_STATUS_INVITED));
    835         assertEquals(CalendarUtilities.BUSY_STATUS_TENTATIVE,
    836                 CalendarUtilities.busyStatusFromAttendeeStatus(
    837                         Attendees.ATTENDEE_STATUS_TENTATIVE));
    838         assertEquals(CalendarUtilities.BUSY_STATUS_BUSY,
    839                 CalendarUtilities.busyStatusFromAttendeeStatus(
    840                         Attendees.ATTENDEE_STATUS_ACCEPTED));
    841     }
    842 
    843     public void testGetUtcAllDayCalendarTime() {
    844         GregorianCalendar correctUtc = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    845         correctUtc.set(2011, 2, 10, 0, 0, 0);
    846         long correctUtcTime = correctUtc.getTimeInMillis();
    847 
    848         TimeZone localTimeZone = TimeZone.getTimeZone("GMT-0700");
    849         GregorianCalendar localCalendar = new GregorianCalendar(localTimeZone);
    850         localCalendar.set(2011, 2, 10, 12, 23, 34);
    851         long localTimeMillis = localCalendar.getTimeInMillis();
    852         long convertedUtcTime =
    853             CalendarUtilities.getUtcAllDayCalendarTime(localTimeMillis, localTimeZone);
    854         // Milliseconds aren't zeroed out and may not be the same
    855         assertEquals(convertedUtcTime/1000, correctUtcTime/1000);
    856 
    857         localTimeZone = TimeZone.getTimeZone("GMT+0700");
    858         localCalendar = new GregorianCalendar(localTimeZone);
    859         localCalendar.set(2011, 2, 10, 12, 23, 34);
    860         localTimeMillis = localCalendar.getTimeInMillis();
    861         convertedUtcTime =
    862             CalendarUtilities.getUtcAllDayCalendarTime(localTimeMillis, localTimeZone);
    863         assertEquals(convertedUtcTime/1000, correctUtcTime/1000);
    864     }
    865 
    866     public void testGetLocalAllDayCalendarTime() {
    867         TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
    868         TimeZone localTimeZone = TimeZone.getTimeZone("GMT-0700");
    869         GregorianCalendar correctLocal = new GregorianCalendar(localTimeZone);
    870         correctLocal.set(2011, 2, 10, 0, 0, 0);
    871         long correctLocalTime = correctLocal.getTimeInMillis();
    872 
    873         GregorianCalendar utcCalendar = new GregorianCalendar(utcTimeZone);
    874         utcCalendar.set(2011, 2, 10, 12, 23, 34);
    875         long utcTimeMillis = utcCalendar.getTimeInMillis();
    876         long convertedLocalTime =
    877             CalendarUtilities.getLocalAllDayCalendarTime(utcTimeMillis, localTimeZone);
    878         // Milliseconds aren't zeroed out and may not be the same
    879         assertEquals(convertedLocalTime/1000, correctLocalTime/1000);
    880 
    881         localTimeZone = TimeZone.getTimeZone("GMT+0700");
    882         correctLocal = new GregorianCalendar(localTimeZone);
    883         correctLocal.set(2011, 2, 10, 0, 0, 0);
    884         correctLocalTime = correctLocal.getTimeInMillis();
    885 
    886         utcCalendar = new GregorianCalendar(utcTimeZone);
    887         utcCalendar.set(2011, 2, 10, 12, 23, 34);
    888         utcTimeMillis = utcCalendar.getTimeInMillis();
    889         convertedLocalTime =
    890             CalendarUtilities.getLocalAllDayCalendarTime(utcTimeMillis, localTimeZone);
    891         // Milliseconds aren't zeroed out and may not be the same
    892         assertEquals(convertedLocalTime/1000, correctLocalTime/1000);
    893     }
    894 
    895     public void testGetIntegerValueAsBoolean() {
    896         ContentValues cv = new ContentValues();
    897         cv.put("A", 1);
    898         cv.put("B", 69);
    899         cv.put("C", 0);
    900         assertTrue(CalendarUtilities.getIntegerValueAsBoolean(cv, "A"));
    901         assertTrue(CalendarUtilities.getIntegerValueAsBoolean(cv, "B"));
    902         assertFalse(CalendarUtilities.getIntegerValueAsBoolean(cv, "C"));
    903         assertFalse(CalendarUtilities.getIntegerValueAsBoolean(cv, "D"));
    904     }
    905 }
    906 
    907     // TODO Planned unit tests
    908     // findNextTransition
    909     // recurrenceFromRrule
    910     // timeZoneToTziStringImpl
    911     // getDSTCalendars
    912     // millisToVCalendarTime
    913     // millisToEasDateTime
    914     // getTrueTransitionMinute
    915     // getTrueTransitionHour
    916 
    917