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