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