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