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