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