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