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 (at) server.com"; 114 private static final String ATTENDEE = "attendee (at) server.com"; 115 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 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 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 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 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 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 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 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 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 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 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 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 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 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 605 public void testUtcOffsetString() { 606 assertEquals(CalendarUtilities.utcOffsetString(540), "+0900"); 607 assertEquals(CalendarUtilities.utcOffsetString(-480), "-0800"); 608 assertEquals(CalendarUtilities.utcOffsetString(0), "+0000"); 609 } 610 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 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 */ 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 */ 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; 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 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 803 String get(String field) { 804 return hash.get(field); 805 } 806 } 807 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 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 */ 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