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