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