15862a85e17e81866ca82a9905577931947fbd44eMarc Blank/* 25862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Copyright (C) 2010 The Android Open Source Project 35862a85e17e81866ca82a9905577931947fbd44eMarc Blank * 45862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 55862a85e17e81866ca82a9905577931947fbd44eMarc Blank * you may not use this file except in compliance with the License. 65862a85e17e81866ca82a9905577931947fbd44eMarc Blank * You may obtain a copy of the License at 75862a85e17e81866ca82a9905577931947fbd44eMarc Blank * 85862a85e17e81866ca82a9905577931947fbd44eMarc Blank * http://www.apache.org/licenses/LICENSE-2.0 95862a85e17e81866ca82a9905577931947fbd44eMarc Blank * 105862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Unless required by applicable law or agreed to in writing, software 115862a85e17e81866ca82a9905577931947fbd44eMarc Blank * distributed under the License is distributed on an "AS IS" BASIS, 125862a85e17e81866ca82a9905577931947fbd44eMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135862a85e17e81866ca82a9905577931947fbd44eMarc Blank * See the License for the specific language governing permissions and 145862a85e17e81866ca82a9905577931947fbd44eMarc Blank * limitations under the License. 155862a85e17e81866ca82a9905577931947fbd44eMarc Blank */ 165862a85e17e81866ca82a9905577931947fbd44eMarc Blank 175862a85e17e81866ca82a9905577931947fbd44eMarc Blankpackage com.android.exchange.utility; 185862a85e17e81866ca82a9905577931947fbd44eMarc Blank 19c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport android.content.ContentResolver; 20c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport android.content.ContentUris; 2177110d3a646dd691d84abd0b1e083385c1418ac5Marc Blankimport android.content.ContentValues; 225c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport android.content.Context; 23c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport android.content.Entity; 24c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport android.content.Entity.NamedContentValues; 25c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport android.content.EntityIterator; 26dafc866120dac68fabbddcc9943e3995894c5244Marc Blankimport android.content.res.Resources; 27185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shraunerimport android.database.Cursor; 2877110d3a646dd691d84abd0b1e083385c1418ac5Marc Blankimport android.net.Uri; 29693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Attendees; 30693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Calendars; 31693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.Events; 32693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErikimport android.provider.CalendarContract.EventsEntity; 33efae936b117c9e4f3056d52fdbfe4d3f261483e5Marc Blankimport android.text.TextUtils; 3477110d3a646dd691d84abd0b1e083385c1418ac5Marc Blankimport android.text.format.Time; 356137d3f2ce68db51926a5e33bf1f57e49bcf8a31Doug Zongkerimport android.util.Base64; 365862a85e17e81866ca82a9905577931947fbd44eMarc Blank 37976e98bfc2dd7f782fc92cc4242f9dcebc2b6858mindypimport com.android.calendarcommon2.DateException; 38976e98bfc2dd7f782fc92cc4242f9dcebc2b6858mindypimport com.android.calendarcommon2.Duration; 39f352bc9f29cacc61b195069e48d5c8b868660694Marc Blankimport com.android.emailcommon.Logging; 4060df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.mail.Address; 4160df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.Account; 4260df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.EmailContent; 4360df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 4460df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 4560df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.Mailbox; 4660df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.service.AccountServiceProxy; 4760df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.utility.Utility; 4860df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.Eas; 4960df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.R; 5060df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.adapter.Serializer; 5160df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.adapter.Tags; 52942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedyimport com.android.mail.utils.LogUtils; 5360df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.google.common.annotations.VisibleForTesting; 5460df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank 5514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blankimport java.io.IOException; 56dafc866120dac68fabbddcc9943e3995894c5244Marc Blankimport java.text.DateFormat; 572f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shraunerimport java.text.ParseException; 58c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport java.util.ArrayList; 595862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.Calendar; 605862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.Date; 615862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.GregorianCalendar; 625862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.HashMap; 635862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.TimeZone; 645862a85e17e81866ca82a9905577931947fbd44eMarc Blank 655862a85e17e81866ca82a9905577931947fbd44eMarc Blankpublic class CalendarUtilities { 6604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik 675862a85e17e81866ca82a9905577931947fbd44eMarc Blank // NOTE: Most definitions in this class are have package visibility for testing purposes 68110837ebff288a75f9bda067c38e2c46797d99b5Alon Albert private static final String TAG = Eas.LOG_TAG; 695862a85e17e81866ca82a9905577931947fbd44eMarc Blank 705862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Time related convenience constants, in milliseconds 715862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int SECONDS = 1000; 725862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MINUTES = SECONDS*60; 735862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int HOURS = MINUTES*60; 74377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static final long DAYS = HOURS*24; 755862a85e17e81866ca82a9905577931947fbd44eMarc Blank 762c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // We want to find a time zone whose DST info is accurate to one minute 772c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static final int STANDARD_DST_PRECISION = MINUTES; 782c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // If we can't find one, we'll try a more lenient standard (this is better than guessing a 792c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // time zone, which is what we otherwise do). Note that this specifically addresses an issue 802c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // seen in some time zones sent by MS Exchange in which the start and end hour differ 812c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // for no apparent reason 822c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static final int LENIENT_DST_PRECISION = 4*HOURS; 832c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank 8404c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik private static final String SYNC_VERSION = Events.SYNC_DATA4; 855862a85e17e81866ca82a9905577931947fbd44eMarc Blank // NOTE All Microsoft data structures are little endian 865862a85e17e81866ca82a9905577931947fbd44eMarc Blank 875862a85e17e81866ca82a9905577931947fbd44eMarc Blank // The following constants relate to standard Microsoft data sizes 885862a85e17e81866ca82a9905577931947fbd44eMarc Blank // For documentation, see http://msdn.microsoft.com/en-us/library/aa505945.aspx 895862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_LONG_SIZE = 4; 905862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_WCHAR_SIZE = 2; 915862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_WORD_SIZE = 2; 925862a85e17e81866ca82a9905577931947fbd44eMarc Blank 935862a85e17e81866ca82a9905577931947fbd44eMarc Blank // The following constants relate to Microsoft's SYSTEMTIME structure 945862a85e17e81866ca82a9905577931947fbd44eMarc Blank // For documentation, see: http://msdn.microsoft.com/en-us/library/ms724950(VS.85).aspx?ppud=4 955862a85e17e81866ca82a9905577931947fbd44eMarc Blank 965862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_YEAR = 0 * MSFT_WORD_SIZE; 975862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_MONTH = 1 * MSFT_WORD_SIZE; 985862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_DAY_OF_WEEK = 2 * MSFT_WORD_SIZE; 995862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_DAY = 3 * MSFT_WORD_SIZE; 1005862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_HOUR = 4 * MSFT_WORD_SIZE; 1015862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_MINUTE = 5 * MSFT_WORD_SIZE; 1025862a85e17e81866ca82a9905577931947fbd44eMarc Blank //static final int MSFT_SYSTEMTIME_SECONDS = 6 * MSFT_WORD_SIZE; 1035862a85e17e81866ca82a9905577931947fbd44eMarc Blank //static final int MSFT_SYSTEMTIME_MILLIS = 7 * MSFT_WORD_SIZE; 1045862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_SIZE = 8*MSFT_WORD_SIZE; 1055862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1065862a85e17e81866ca82a9905577931947fbd44eMarc Blank // The following constants relate to Microsoft's TIME_ZONE_INFORMATION structure 1075862a85e17e81866ca82a9905577931947fbd44eMarc Blank // For documentation, see http://msdn.microsoft.com/en-us/library/ms725481(VS.85).aspx 108bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank static final int MSFT_TIME_ZONE_STRING_SIZE = 32; 109bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank 1105862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_BIAS_OFFSET = 0; 1115862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_STANDARD_NAME_OFFSET = 1125862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_BIAS_OFFSET + MSFT_LONG_SIZE; 1135862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_STANDARD_DATE_OFFSET = 114bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank MSFT_TIME_ZONE_STANDARD_NAME_OFFSET + (MSFT_WCHAR_SIZE*MSFT_TIME_ZONE_STRING_SIZE); 1155862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_STANDARD_BIAS_OFFSET = 1165862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_STANDARD_DATE_OFFSET + MSFT_SYSTEMTIME_SIZE; 1175862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET = 1185862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_STANDARD_BIAS_OFFSET + MSFT_LONG_SIZE; 1195862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET = 120bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET + (MSFT_WCHAR_SIZE*MSFT_TIME_ZONE_STRING_SIZE); 1215862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET = 1225862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET + MSFT_SYSTEMTIME_SIZE; 1235862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_SIZE = 1245862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET + MSFT_LONG_SIZE; 1255862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1265862a85e17e81866ca82a9905577931947fbd44eMarc Blank // TimeZone cache; we parse/decode as little as possible, because the process is quite slow 1275862a85e17e81866ca82a9905577931947fbd44eMarc Blank private static HashMap<String, TimeZone> sTimeZoneCache = new HashMap<String, TimeZone>(); 128377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // TZI string cache; we keep around our encoded TimeZoneInformation strings 129377230593dca7cb01483bfaf93959e5821f5f028Marc Blank private static HashMap<TimeZone, String> sTziStringCache = new HashMap<TimeZone, String>(); 1305862a85e17e81866ca82a9905577931947fbd44eMarc Blank 131270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC"); 132937af5abcbc1268f22a3058b00835c74ba20f116RoboErik // Default, Popup 13380a8e57ce2dc2695ed6f35599d326090e4ad9faeRoboErik private static final String ALLOWED_REMINDER_TYPES = "0,1"; 134937af5abcbc1268f22a3058b00835c74ba20f116RoboErik // None, required, optional 135937af5abcbc1268f22a3058b00835c74ba20f116RoboErik private static final String ALLOWED_ATTENDEE_TYPES = "0,1,2"; 136937af5abcbc1268f22a3058b00835c74ba20f116RoboErik // Busy, free, tentative 137937af5abcbc1268f22a3058b00835c74ba20f116RoboErik private static final String ALLOWED_AVAILABILITIES = "0,1,2"; 138270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank 1395862a85e17e81866ca82a9905577931947fbd44eMarc Blank // There is no type 4 (thus, the "") 1405862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final String[] sTypeToFreq = 1415862a85e17e81866ca82a9905577931947fbd44eMarc Blank new String[] {"DAILY", "WEEKLY", "MONTHLY", "MONTHLY", "", "YEARLY", "YEARLY"}; 1425862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1435862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final String[] sDayTokens = 1445862a85e17e81866ca82a9905577931947fbd44eMarc Blank new String[] {"SU", "MO", "TU", "WE", "TH", "FR", "SA"}; 1455862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1465862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final String[] sTwoCharacterNumbers = 1475862a85e17e81866ca82a9905577931947fbd44eMarc Blank new String[] {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}; 1485862a85e17e81866ca82a9905577931947fbd44eMarc Blank 149f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Bits used in EAS recurrences for days of the week 150f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_SUNDAY = 1<<0; 151f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_MONDAY = 1<<1; 152f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_TUESDAY = 1<<2; 153f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_WEDNESDAY = 1<<3; 154f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_THURSDAY = 1<<4; 155f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_FRIDAY = 1<<5; 156f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_SATURDAY = 1<<6; 157f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_WEEKDAYS = 158f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank EAS_MONDAY | EAS_TUESDAY | EAS_WEDNESDAY | EAS_THURSDAY | EAS_FRIDAY; 159f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_WEEKENDS = EAS_SATURDAY | EAS_SUNDAY; 160f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank 161377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static final int sCurrentYear = new GregorianCalendar().get(Calendar.YEAR); 162377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static final TimeZone sGmtTimeZone = TimeZone.getTimeZone("GMT"); 163377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 164bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank private static final String ICALENDAR_ATTENDEE = "ATTENDEE;ROLE=REQ-PARTICIPANT"; 165bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_CANCEL = ICALENDAR_ATTENDEE; 166bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_INVITE = 167bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=NEEDS-ACTION;RSVP=TRUE"; 168bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_ACCEPT = 169bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=ACCEPTED"; 170bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_DECLINE = 171bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=DECLINED"; 172bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_TENTATIVE = 173bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=TENTATIVE"; 174c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 175e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // Note that these constants apply to Calendar items 176e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // For future reference: MeetingRequest data can also include free/busy information, but the 177e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // constants for these four options in MeetingRequest data have different values! 178e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // See [MS-ASCAL] 2.2.2.8 for Calendar BusyStatus 179e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // See [MS-EMAIL] 2.2.2.34 for MeetingRequest BusyStatus 180e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_FREE = 0; 181e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_TENTATIVE = 1; 182e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_BUSY = 2; 183e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_OUT_OF_OFFICE = 3; 184dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank 18565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank // Note that these constants apply to Calendar items, and are used in EAS 14+ 18665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank // See [MS-ASCAL] 2.2.2.22 for Calendar ResponseType 18765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_NONE = 0; 18865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_ORGANIZER = 1; 18965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_TENTATIVE = 2; 19065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_ACCEPTED = 3; 19165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_DECLINED = 4; 19265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_NOT_RESPONDED = 5; 19365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank 1945862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Return a 4-byte long from a byte array (little endian) 1955862a85e17e81866ca82a9905577931947fbd44eMarc Blank static int getLong(byte[] bytes, int offset) { 1965862a85e17e81866ca82a9905577931947fbd44eMarc Blank return (bytes[offset++] & 0xFF) | ((bytes[offset++] & 0xFF) << 8) | 1975862a85e17e81866ca82a9905577931947fbd44eMarc Blank ((bytes[offset++] & 0xFF) << 16) | ((bytes[offset] & 0xFF) << 24); 1985862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 1995862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2005862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Put a 4-byte long into a byte array (little endian) 2015862a85e17e81866ca82a9905577931947fbd44eMarc Blank static void setLong(byte[] bytes, int offset, int value) { 2025862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) (value & 0xFF); 2035862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) ((value >> 8) & 0xFF); 2045862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) ((value >> 16) & 0xFF); 2055862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset] = (byte) ((value >> 24) & 0xFF); 2065862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2075862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2085862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Return a 2-byte word from a byte array (little endian) 2095862a85e17e81866ca82a9905577931947fbd44eMarc Blank static int getWord(byte[] bytes, int offset) { 2105862a85e17e81866ca82a9905577931947fbd44eMarc Blank return (bytes[offset++] & 0xFF) | ((bytes[offset] & 0xFF) << 8); 2115862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2125862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2135862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Put a 2-byte word into a byte array (little endian) 2145862a85e17e81866ca82a9905577931947fbd44eMarc Blank static void setWord(byte[] bytes, int offset, int value) { 2155862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) (value & 0xFF); 2165862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset] = (byte) ((value >> 8) & 0xFF); 2175862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2185862a85e17e81866ca82a9905577931947fbd44eMarc Blank 219bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank static String getString(byte[] bytes, int offset, int size) { 220bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank StringBuilder sb = new StringBuilder(); 221bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank for (int i = 0; i < size; i++) { 222bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank int ch = bytes[offset + i]; 223bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank if (ch == 0) { 224bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank break; 225bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } else { 226bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank sb.append((char)ch); 227bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } 228bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } 229bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank return sb.toString(); 230bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } 231bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank 2325862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Internal structure for storing a time zone date from a SYSTEMTIME structure 2335862a85e17e81866ca82a9905577931947fbd44eMarc Blank // This date represents either the start or the end time for DST 2345862a85e17e81866ca82a9905577931947fbd44eMarc Blank static class TimeZoneDate { 2355862a85e17e81866ca82a9905577931947fbd44eMarc Blank String year; 2365862a85e17e81866ca82a9905577931947fbd44eMarc Blank int month; 2375862a85e17e81866ca82a9905577931947fbd44eMarc Blank int dayOfWeek; 2385862a85e17e81866ca82a9905577931947fbd44eMarc Blank int day; 2395862a85e17e81866ca82a9905577931947fbd44eMarc Blank int time; 2405862a85e17e81866ca82a9905577931947fbd44eMarc Blank int hour; 2415862a85e17e81866ca82a9905577931947fbd44eMarc Blank int minute; 2425862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2435862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2444868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank @VisibleForTesting 2454868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank static void clearTimeZoneCache() { 2464868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank sTimeZoneCache.clear(); 2474868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank } 2484868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank 249820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static void putRuleIntoTimeZoneInformation(byte[] bytes, int offset, RRule rrule, int hour, 250820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int minute) { 251820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // MSFT months are 1 based, same as RRule 252820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MONTH, rrule.month); 253820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // MSFT day of week starts w/ Sunday = 0; RRule starts w/ Sunday = 1 254820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY_OF_WEEK, rrule.dayOfWeek - 1); 255820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // 5 means "last" in MSFT land; for RRule, it's -1 256820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY, rrule.week < 0 ? 5 : rrule.week); 257820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Turn hours/minutes into ms from midnight (per TimeZone) 258820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_HOUR, hour); 259820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MINUTE, minute); 260820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 261820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 26210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank // Write a transition time into SYSTEMTIME data (via an offset into a byte array) 26310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank static void putTransitionMillisIntoSystemTime(byte[] bytes, int offset, long millis) { 264377230593dca7cb01483bfaf93959e5821f5f028Marc Blank GregorianCalendar cal = new GregorianCalendar(TimeZone.getDefault()); 265377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // Round to the next highest minute; we always write seconds as zero 266377230593dca7cb01483bfaf93959e5821f5f028Marc Blank cal.setTimeInMillis(millis + 30*SECONDS); 267377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 268377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // MSFT months are 1 based; TimeZone is 0 based 269377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MONTH, cal.get(Calendar.MONTH) + 1); 270377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // MSFT day of week starts w/ Sunday = 0; TimeZone starts w/ Sunday = 1 271377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY_OF_WEEK, cal.get(Calendar.DAY_OF_WEEK) - 1); 272377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 273377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // Get the "day" in TimeZone format 274377230593dca7cb01483bfaf93959e5821f5f028Marc Blank int wom = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); 275377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // 5 means "last" in MSFT land; for TimeZone, it's -1 276377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY, wom < 0 ? 5 : wom); 277377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 278377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // Turn hours/minutes into ms from midnight (per TimeZone) 27910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_HOUR, getTrueTransitionHour(cal)); 28010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MINUTE, getTrueTransitionMinute(cal)); 281377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 282377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 2835862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Build a TimeZoneDate structure from a SYSTEMTIME within a byte array at a given offset 2845862a85e17e81866ca82a9905577931947fbd44eMarc Blank static TimeZoneDate getTimeZoneDateFromSystemTime(byte[] bytes, int offset) { 2855862a85e17e81866ca82a9905577931947fbd44eMarc Blank TimeZoneDate tzd = new TimeZoneDate(); 2865862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2875862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT year is an int; TimeZone is a String 2885862a85e17e81866ca82a9905577931947fbd44eMarc Blank int num = getWord(bytes, offset + MSFT_SYSTEMTIME_YEAR); 2895862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.year = Integer.toString(num); 2905862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2915862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT month = 0 means no daylight time 2925862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT months are 1 based; TimeZone is 0 based 2935862a85e17e81866ca82a9905577931947fbd44eMarc Blank num = getWord(bytes, offset + MSFT_SYSTEMTIME_MONTH); 2945862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (num == 0) { 2955862a85e17e81866ca82a9905577931947fbd44eMarc Blank return null; 2965862a85e17e81866ca82a9905577931947fbd44eMarc Blank } else { 2975862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.month = num -1; 2985862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2995862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3005862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT day of week starts w/ Sunday = 0; TimeZone starts w/ Sunday = 1 3015862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.dayOfWeek = getWord(bytes, offset + MSFT_SYSTEMTIME_DAY_OF_WEEK) + 1; 3025862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3035862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Get the "day" in TimeZone format 3045862a85e17e81866ca82a9905577931947fbd44eMarc Blank num = getWord(bytes, offset + MSFT_SYSTEMTIME_DAY); 3055862a85e17e81866ca82a9905577931947fbd44eMarc Blank // 5 means "last" in MSFT land; for TimeZone, it's -1 3065862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (num == 5) { 3075862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.day = -1; 3085862a85e17e81866ca82a9905577931947fbd44eMarc Blank } else { 3095862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.day = num; 3105862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3115862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3125862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Turn hours/minutes into ms from midnight (per TimeZone) 3135862a85e17e81866ca82a9905577931947fbd44eMarc Blank int hour = getWord(bytes, offset + MSFT_SYSTEMTIME_HOUR); 3145862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.hour = hour; 3155862a85e17e81866ca82a9905577931947fbd44eMarc Blank int minute = getWord(bytes, offset + MSFT_SYSTEMTIME_MINUTE); 3165862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.minute = minute; 3175862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.time = (hour*HOURS) + (minute*MINUTES); 3185862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3195862a85e17e81866ca82a9905577931947fbd44eMarc Blank return tzd; 3205862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3215862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3225862a85e17e81866ca82a9905577931947fbd44eMarc Blank /** 3235862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Build a GregorianCalendar, based on a time zone and TimeZoneDate. 3245862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @param timeZone the time zone we're checking 3255862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @param tzd the TimeZoneDate we're interested in 3265862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @return a GregorianCalendar with the given time zone and date 3275862a85e17e81866ca82a9905577931947fbd44eMarc Blank */ 32879268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static long getMillisAtTimeZoneDateTransition(TimeZone timeZone, TimeZoneDate tzd) { 3295862a85e17e81866ca82a9905577931947fbd44eMarc Blank GregorianCalendar testCalendar = new GregorianCalendar(timeZone); 330377230593dca7cb01483bfaf93959e5821f5f028Marc Blank testCalendar.set(GregorianCalendar.YEAR, sCurrentYear); 3315862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.MONTH, tzd.month); 3325862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.DAY_OF_WEEK, tzd.dayOfWeek); 3335862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.DAY_OF_WEEK_IN_MONTH, tzd.day); 3345862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.HOUR_OF_DAY, tzd.hour); 3355862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.MINUTE, tzd.minute); 33679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank testCalendar.set(GregorianCalendar.SECOND, 0); 33779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank return testCalendar.getTimeInMillis(); 3385862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3395862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3405862a85e17e81866ca82a9905577931947fbd44eMarc Blank /** 341820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Return a GregorianCalendar representing the first standard/daylight transition between a 342820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * start time and an end time in the given time zone 343820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz a TimeZone the time zone in which we're looking for transitions 344377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param startTime the start time for the test 345377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param endTime the end time for the test 346377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param startInDaylightTime whether daylight time is in effect at the startTime 347820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return a GregorianCalendar representing the transition or null if none 348377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 34979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static GregorianCalendar findTransitionDate(TimeZone tz, long startTime, 35010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank long endTime, boolean startInDaylightTime) { 351377230593dca7cb01483bfaf93959e5821f5f028Marc Blank long startingEndTime = endTime; 352377230593dca7cb01483bfaf93959e5821f5f028Marc Blank Date date = null; 35310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank 354820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll keep splitting the difference until we're within a minute 355377230593dca7cb01483bfaf93959e5821f5f028Marc Blank while ((endTime - startTime) > MINUTES) { 356377230593dca7cb01483bfaf93959e5821f5f028Marc Blank long checkTime = ((startTime + endTime) / 2) + 1; 357377230593dca7cb01483bfaf93959e5821f5f028Marc Blank date = new Date(checkTime); 35810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank boolean inDaylightTime = tz.inDaylightTime(date); 35910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank if (inDaylightTime != startInDaylightTime) { 360377230593dca7cb01483bfaf93959e5821f5f028Marc Blank endTime = checkTime; 361377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } else { 362377230593dca7cb01483bfaf93959e5821f5f028Marc Blank startTime = checkTime; 363377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 364377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 365820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 366820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If these are the same, we're really messed up; return null 367377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (endTime == startingEndTime) { 368820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 369377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 370820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 37110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank // Set up our calendar and return it 372820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar calendar = new GregorianCalendar(tz); 37310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank calendar.setTimeInMillis(startTime); 374820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return calendar; 375377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 376377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 377377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 378377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Return a Base64 representation of a MSFT TIME_ZONE_INFORMATION structure from a TimeZone 379377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * that might be found in an Event; use cached result, if possible 380377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param tz the TimeZone 381377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return the Base64 String representing a Microsoft TIME_ZONE_INFORMATION element 382377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 383377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static public String timeZoneToTziString(TimeZone tz) { 384377230593dca7cb01483bfaf93959e5821f5f028Marc Blank String tziString = sTziStringCache.get(tz); 385377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (tziString != null) { 386377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (Eas.USER_LOG) { 387bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, "TZI string for " + tz.getDisplayName() + 388385a0be662509754e687bcfa9813208b050bf951Marc Blank " found in cache."); 389377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 390377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return tziString; 391377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 392377230593dca7cb01483bfaf93959e5821f5f028Marc Blank tziString = timeZoneToTziStringImpl(tz); 393377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sTziStringCache.put(tz, tziString); 394377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return tziString; 395377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 396377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 397377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 398820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * A class for storing RRULE information. The RRULE members can be accessed individually or 399820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * an RRULE string can be created with toString() 400820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 401820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static class RRule { 402820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static final int RRULE_NONE = 0; 403820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static final int RRULE_DAY_WEEK = 1; 404820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static final int RRULE_DATE = 2; 405820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 406820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int type; 407820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int dayOfWeek; 408820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int week; 409820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int month; 410820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int date; 411820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 412820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 413820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Create an RRULE based on month and date 414820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _month the month (1 = JAN, 12 = DEC) 415820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _date the date in the month (1-31) 416820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 417820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule(int _month, int _date) { 418820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank type = RRULE_DATE; 419820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank month = _month; 420820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank date = _date; 421820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 422820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 423820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 424820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Create an RRULE based on month, day of week, and week # 425820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _month the month (1 = JAN, 12 = DEC) 426820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _dayOfWeek the day of the week (1 = SU, 7 = SA) 427820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _week the week in the month (1-5 or -1 for last) 428820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 429820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule(int _month, int _dayOfWeek, int _week) { 430820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank type = RRULE_DAY_WEEK; 431820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank month = _month; 432820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank dayOfWeek = _dayOfWeek; 433820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank week = _week; 434820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 435820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 436820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank @Override 437820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank public String toString() { 438820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (type == RRULE_DAY_WEEK) { 439820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return "FREQ=YEARLY;BYMONTH=" + month + ";BYDAY=" + week + 440820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sDayTokens[dayOfWeek - 1]; 441820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 442820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return "FREQ=YEARLY;BYMONTH=" + month + ";BYMONTHDAY=" + date; 443820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 444820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 445820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 446820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 447820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 448820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Generate an RRULE string for an array of GregorianCalendars, if possible. For now, we are 449820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * only looking for rules based on the same date in a month or a specific instance of a day of 450820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the week in a month (e.g. 2nd Tuesday or last Friday). Indeed, these are the only kinds of 451820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * rules used in the current tzinfo database. 452820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param calendars an array of GregorianCalendar, set to a series of transition times in 453820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * consecutive years starting with the current year 454820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return an RRULE or null if none could be inferred from the calendars 455820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 45679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static RRule inferRRuleFromCalendars(GregorianCalendar[] calendars) { 457820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Let's see if we can make a rule about these 458820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar calendar = calendars[0]; 459820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (calendar == null) return null; 460820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int month = calendar.get(Calendar.MONTH); 461820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int date = calendar.get(Calendar.DAY_OF_MONTH); 462820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); 463820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int week = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH); 464820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int maxWeek = calendar.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); 465820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean dateRule = false; 466820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean dayOfWeekRule = false; 467820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 1; i < calendars.length; i++) { 468820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar cal = calendars[i]; 469820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal == null) return null; 470820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If it's not the same month, there's no rule 471820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal.get(Calendar.MONTH) != month) { 472820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 473820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (dayOfWeek == cal.get(Calendar.DAY_OF_WEEK)) { 474820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Ok, it seems to be the same day of the week 475820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dateRule) { 476820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 477820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 478820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank dayOfWeekRule = true; 479820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int thisWeek = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); 480820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (week != thisWeek) { 481820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (week < 0 || week == maxWeek) { 482820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int thisMaxWeek = cal.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); 483820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (thisWeek == thisMaxWeek) { 484820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use -1 (i.e. last) week 485820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank week = -1; 486820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank continue; 487820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 488820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 489820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 490820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 491820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (date == cal.get(Calendar.DAY_OF_MONTH)) { 492820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Maybe the same day of the month? 493820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dayOfWeekRule) { 494820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 495820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 496820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank dateRule = true; 497820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 498820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 499820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 500820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 501820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 502820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dateRule) { 503820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return new RRule(month + 1, date); 504820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 505820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // sDayTokens is 0 based (SU = 0); Calendar days of week are 1 based (SU = 1) 506820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // iCalendar months are 1 based; Calendar months are 0 based 507820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // So we adjust these when building the string 508820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return new RRule(month + 1, dayOfWeek, week); 509820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 510820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 511820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 512820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Generate an rfc2445 utcOffset from minutes offset from GMT 513820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * These look like +0800 or -0100 514820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param offsetMinutes minutes offset from GMT (east is positive, west is negative 515820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return a utcOffset 516820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 51779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static String utcOffsetString(int offsetMinutes) { 518820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank StringBuilder sb = new StringBuilder(); 519820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int hours = offsetMinutes / 60; 520820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hours < 0) { 521820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('-'); 522820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank hours = 0 - hours; 523820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 524820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('+'); 525820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 526820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int minutes = offsetMinutes % 60; 527820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hours < 10) { 528820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('0'); 529820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 530820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(hours); 531820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (minutes < 10) { 532820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('0'); 533820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 534820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(minutes); 535820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return sb.toString(); 536820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 537820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 538820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 539820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Fill the passed in GregorianCalendars arrays with DST transition information for this and 540820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the following years (based on the length of the arrays) 541820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz the time zone 542820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param toDaylightCalendars an array of GregorianCalendars, one for each year, representing 543820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the transition to daylight time 544820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param toStandardCalendars an array of GregorianCalendars, one for each year, representing 545820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the transition to standard time 546820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return true if transitions could be found for all years, false otherwise 547820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 548820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static boolean getDSTCalendars(TimeZone tz, GregorianCalendar[] toDaylightCalendars, 549820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toStandardCalendars) { 550820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use the length of the arrays to determine how many years to check 551820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int maxYears = toDaylightCalendars.length; 552820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (toStandardCalendars.length != maxYears) { 553820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return false; 554820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 555820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Get the transitions for this year and the next few years 556820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 0; i < maxYears; i++) { 557820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar cal = new GregorianCalendar(tz); 558820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal.set(sCurrentYear + i, Calendar.JANUARY, 1, 0, 0, 0); 559820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long startTime = cal.getTimeInMillis(); 560820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Calculate end of year; no need to be insanely precise 561820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long endOfYearTime = startTime + (365*DAYS) + (DAYS>>2); 562820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank Date date = new Date(startTime); 563820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean startInDaylightTime = tz.inDaylightTime(date); 564820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Find the first transition, and store 565820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal = findTransitionDate(tz, startTime, endOfYearTime, startInDaylightTime); 566820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal == null) { 567820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return false; 568820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (startInDaylightTime) { 569820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toStandardCalendars[i] = cal; 570820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 571820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toDaylightCalendars[i] = cal; 572820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 573820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Find the second transition, and store 574820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal = findTransitionDate(tz, startTime, endOfYearTime, !startInDaylightTime); 575820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal == null) { 576820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return false; 577820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (startInDaylightTime) { 578820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toDaylightCalendars[i] = cal; 579820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 580820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toStandardCalendars[i] = cal; 581820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 582820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 583820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return true; 584820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 585820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 586820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 587820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Write out the STANDARD block of VTIMEZONE and end the VTIMEZONE 588820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param writer the SimpleIcsWriter we're using 589820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz the time zone 590820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param offsetString the offset string in VTIMEZONE format (e.g. +0800) 591820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @throws IOException 592820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 593820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static private void writeNoDST(SimpleIcsWriter writer, TimeZone tz, String offsetString) 594820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank throws IOException { 595820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "STANDARD"); 596820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETFROM", offsetString); 597820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETTO", offsetString); 598820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Might as well use start of epoch for start date 599820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("DTSTART", millisToEasDateTime(0L)); 600820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "STANDARD"); 601820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "VTIMEZONE"); 602820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 603820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 604820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** Write a VTIMEZONE block for a given TimeZone into a SimpleIcsWriter 605820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz the TimeZone to be used in the conversion 606820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param writer the SimpleIcsWriter to be used 607820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @throws IOException 608820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 60979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static void timeZoneToVTimezone(TimeZone tz, SimpleIcsWriter writer) 610820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank throws IOException { 611820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use these regardless of whether there's DST in this time zone or not 612820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int rawOffsetMinutes = tz.getRawOffset() / MINUTES; 613820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank String standardOffsetString = utcOffsetString(rawOffsetMinutes); 614820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 615820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Preamble for all of our VTIMEZONEs 616820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "VTIMEZONE"); 617820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZID", tz.getID()); 618820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("X-LIC-LOCATION", tz.getDisplayName()); 619820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 620820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Simplest case is no daylight time 621820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (!tz.useDaylightTime()) { 622820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writeNoDST(writer, tz, standardOffsetString); 623820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return; 624820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 625820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 626820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int maxYears = 3; 627820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toDaylightCalendars = new GregorianCalendar[maxYears]; 628820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toStandardCalendars = new GregorianCalendar[maxYears]; 629820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (!getDSTCalendars(tz, toDaylightCalendars, toStandardCalendars)) { 630820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writeNoDST(writer, tz, standardOffsetString); 631820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return; 632820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 633820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Try to find a rule to cover these yeras 634820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule daylightRule = inferRRuleFromCalendars(toDaylightCalendars); 635820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule standardRule = inferRRuleFromCalendars(toStandardCalendars); 636820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank String daylightOffsetString = 637820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank utcOffsetString(rawOffsetMinutes + (tz.getDSTSavings() / MINUTES)); 638820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use RRULE's if we found both 639820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Otherwise we write the first as DTSTART and the others as RDATE 640820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean hasRule = daylightRule != null && standardRule != null; 641820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 642820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write the DAYLIGHT block 643820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "DAYLIGHT"); 644820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETFROM", standardOffsetString); 645820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETTO", daylightOffsetString); 646820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("DTSTART", 64710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank transitionMillisToVCalendarTime( 64810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank toDaylightCalendars[0].getTimeInMillis(), tz, true)); 649820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hasRule) { 650820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("RRULE", daylightRule.toString()); 651820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 652820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 1; i < maxYears; i++) { 65310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank writer.writeTag("RDATE", transitionMillisToVCalendarTime( 654820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toDaylightCalendars[i].getTimeInMillis(), tz, true)); 655820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 656820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 657820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "DAYLIGHT"); 658820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write the STANDARD block 659820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "STANDARD"); 660820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETFROM", daylightOffsetString); 661820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETTO", standardOffsetString); 662820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("DTSTART", 66310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank transitionMillisToVCalendarTime( 66410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank toStandardCalendars[0].getTimeInMillis(), tz, false)); 665820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hasRule) { 666820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("RRULE", standardRule.toString()); 667820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 668820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 1; i < maxYears; i++) { 66910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank writer.writeTag("RDATE", transitionMillisToVCalendarTime( 670820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toStandardCalendars[i].getTimeInMillis(), tz, true)); 671820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 672820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 673820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "STANDARD"); 674820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // And we're done 675820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "VTIMEZONE"); 676820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 677820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 678820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 679820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Find the next transition to occur (i.e. after the current date/time) 680820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param transitions calendars representing transitions to/from DST 681820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return millis for the first transition after the current date/time 682820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 68379268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static long findNextTransition(long startingMillis, GregorianCalendar[] transitions) { 684820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (GregorianCalendar transition: transitions) { 685820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long transitionMillis = transition.getTimeInMillis(); 686820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (transitionMillis > startingMillis) { 687820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return transitionMillis; 688820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 689820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 690820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return 0; 691820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 692820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 693820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 694377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Calculate the Base64 representation of a MSFT TIME_ZONE_INFORMATION structure from a TimeZone 695377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * that might be found in an Event. Since the internal representation of the TimeZone is hidden 696377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * from us we'll find the DST transitions and build the structure from that information 697377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param tz the TimeZone 698377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return the Base64 String representing a Microsoft TIME_ZONE_INFORMATION element 699377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 70079268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static String timeZoneToTziStringImpl(TimeZone tz) { 701377230593dca7cb01483bfaf93959e5821f5f028Marc Blank String tziString; 702377230593dca7cb01483bfaf93959e5821f5f028Marc Blank byte[] tziBytes = new byte[MSFT_TIME_ZONE_SIZE]; 703377230593dca7cb01483bfaf93959e5821f5f028Marc Blank int standardBias = - tz.getRawOffset(); 704377230593dca7cb01483bfaf93959e5821f5f028Marc Blank standardBias /= 60*SECONDS; 705377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setLong(tziBytes, MSFT_TIME_ZONE_BIAS_OFFSET, standardBias); 706820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If this time zone has daylight savings time, we need to do more work 707377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (tz.useDaylightTime()) { 708820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toDaylightCalendars = new GregorianCalendar[3]; 709820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toStandardCalendars = new GregorianCalendar[3]; 710820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // See if we can get transitions for a few years; if not, we can't generate DST info 711820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // for this time zone 712820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (getDSTCalendars(tz, toDaylightCalendars, toStandardCalendars)) { 713820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Try to find a rule to cover these years 714820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule daylightRule = inferRRuleFromCalendars(toDaylightCalendars); 715820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule standardRule = inferRRuleFromCalendars(toStandardCalendars); 716820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if ((daylightRule != null) && (daylightRule.type == RRule.RRULE_DAY_WEEK) && 717820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank (standardRule != null) && (standardRule.type == RRule.RRULE_DAY_WEEK)) { 718820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We need both rules and they have to be DAY/WEEK type 719820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write month, day of week, week, hour, minute 720820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank putRuleIntoTimeZoneInformation(tziBytes, MSFT_TIME_ZONE_STANDARD_DATE_OFFSET, 721820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank standardRule, 72210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionHour(toStandardCalendars[0]), 72310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionMinute(toStandardCalendars[0])); 724820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank putRuleIntoTimeZoneInformation(tziBytes, MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET, 725820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank daylightRule, 72610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionHour(toDaylightCalendars[0]), 72710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionMinute(toDaylightCalendars[0])); 728820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 729820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If there's no rule, we'll use the first transition to standard/to daylight 730820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // And indicate that it's just for this year... 731820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long now = System.currentTimeMillis(); 732820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long standardTransition = findNextTransition(now, toStandardCalendars); 733820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long daylightTransition = findNextTransition(now, toDaylightCalendars); 734820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If we can't find transitions, we can't do DST 735820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (standardTransition != 0 && daylightTransition != 0) { 73610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank putTransitionMillisIntoSystemTime(tziBytes, 73710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank MSFT_TIME_ZONE_STANDARD_DATE_OFFSET, standardTransition); 73810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank putTransitionMillisIntoSystemTime(tziBytes, 73910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET, daylightTransition); 740820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 741820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 742377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 743820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int dstOffset = tz.getDSTSavings(); 744820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setLong(tziBytes, MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET, - dstOffset / MINUTES); 745377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 7467e85a8d17b2b0c80bd04306db5a698ac3b91deaaMakoto Onuki byte[] tziEncodedBytes = Base64.encode(tziBytes, Base64.NO_WRAP); 747377230593dca7cb01483bfaf93959e5821f5f028Marc Blank tziString = new String(tziEncodedBytes); 748377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return tziString; 749377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 750377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 751377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 7525862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Given a String as directly read from EAS, returns a TimeZone corresponding to that String 7535862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @param timeZoneString the String read from the server 7542c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * @param precision the number of milliseconds of precision in TimeZone determination 7555862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @return the TimeZone, or TimeZone.getDefault() if not found 7565862a85e17e81866ca82a9905577931947fbd44eMarc Blank */ 7572c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank @VisibleForTesting 7582c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static TimeZone tziStringToTimeZone(String timeZoneString, int precision) { 7595862a85e17e81866ca82a9905577931947fbd44eMarc Blank // If we have this time zone cached, use that value and return 7605862a85e17e81866ca82a9905577931947fbd44eMarc Blank TimeZone timeZone = sTimeZoneCache.get(timeZoneString); 7615862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (timeZone != null) { 7625862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (Eas.USER_LOG) { 763bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, " Using cached TimeZone " + timeZone.getID()); 764377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 765377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } else { 7662c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank timeZone = tziStringToTimeZoneImpl(timeZoneString, precision); 767377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (timeZone == null) { 768377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // If we don't find a match, we just return the current TimeZone. In theory, this 769377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // shouldn't be happening... 770bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, "TimeZone not found using default: " + timeZoneString); 771377230593dca7cb01483bfaf93959e5821f5f028Marc Blank timeZone = TimeZone.getDefault(); 7725862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 773377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sTimeZoneCache.put(timeZoneString, timeZone); 7745862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 775377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return timeZone; 776377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 7775862a85e17e81866ca82a9905577931947fbd44eMarc Blank 778377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 7792c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * The standard entry to EAS time zone conversion, using one minute as the precision 7802c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank */ 7812c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static public TimeZone tziStringToTimeZone(String timeZoneString) { 7822c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank return tziStringToTimeZone(timeZoneString, MINUTES); 7832c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank } 7842c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank 785e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank static private boolean hasTimeZoneId(String[] timeZoneIds, String id) { 786e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank for (String timeZoneId: timeZoneIds) { 787e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank if (id.equals(timeZoneId)) { 788e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank return true; 789e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } 790e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } 791e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank return false; 792e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } 793e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank 7942c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank /** 795377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Given a String as directly read from EAS, tries to find a TimeZone in the database of all 7962c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * time zones that corresponds to that String. If the test time zone string includes DST and 7972c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * we don't find a match, and we're using standard precision, we try again with lenient 7982c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * precision, which is a bit better than guessing 799377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param timeZoneString the String read from the server 80079268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank * @return the TimeZone, or null if not found 801377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 8022c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static TimeZone tziStringToTimeZoneImpl(String timeZoneString, int precision) { 803377230593dca7cb01483bfaf93959e5821f5f028Marc Blank TimeZone timeZone = null; 8045862a85e17e81866ca82a9905577931947fbd44eMarc Blank // First, we need to decode the base64 string 8057e85a8d17b2b0c80bd04306db5a698ac3b91deaaMakoto Onuki byte[] timeZoneBytes = Base64.decode(timeZoneString, Base64.DEFAULT); 8065862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8075862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Then, we get the bias (similar to a rawOffset); for TimeZone, we need ms 8085862a85e17e81866ca82a9905577931947fbd44eMarc Blank // but EAS gives us minutes, so do the conversion. Note that EAS is the bias that's added 8095862a85e17e81866ca82a9905577931947fbd44eMarc Blank // to the time zone to reach UTC; our library uses the time from UTC to our time zone, so 8105862a85e17e81866ca82a9905577931947fbd44eMarc Blank // we need to change the sign 8115862a85e17e81866ca82a9905577931947fbd44eMarc Blank int bias = -1 * getLong(timeZoneBytes, MSFT_TIME_ZONE_BIAS_OFFSET) * MINUTES; 8125862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8135862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Get all of the time zones with the bias as a rawOffset; if there aren't any, we return 8145862a85e17e81866ca82a9905577931947fbd44eMarc Blank // the default time zone 8155862a85e17e81866ca82a9905577931947fbd44eMarc Blank String[] zoneIds = TimeZone.getAvailableIDs(bias); 8165862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (zoneIds.length > 0) { 8175862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Try to find an existing TimeZone from the data provided by EAS 8185862a85e17e81866ca82a9905577931947fbd44eMarc Blank // We start by pulling out the date that standard time begins 8195862a85e17e81866ca82a9905577931947fbd44eMarc Blank TimeZoneDate dstEnd = 8205862a85e17e81866ca82a9905577931947fbd44eMarc Blank getTimeZoneDateFromSystemTime(timeZoneBytes, MSFT_TIME_ZONE_STANDARD_DATE_OFFSET); 8215862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dstEnd == null) { 8225bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // If the default time zone is a match 8235bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank TimeZone defaultTimeZone = TimeZone.getDefault(); 8245bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (!defaultTimeZone.useDaylightTime() && 825e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank hasTimeZoneId(zoneIds, defaultTimeZone.getID())) { 8265bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (Eas.USER_LOG) { 827bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, "TimeZone without DST found to be default: " + 8285bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank defaultTimeZone.getID()); 8295bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8305bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank return defaultTimeZone; 8315bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8325862a85e17e81866ca82a9905577931947fbd44eMarc Blank // In this case, there is no daylight savings time, so the only interesting data 8335bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // for possible matches is the offset and DST availability; we'll take the first 8345bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // match for those 8355bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank for (String zoneId: zoneIds) { 8365bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank timeZone = TimeZone.getTimeZone(zoneId); 8375bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (!timeZone.useDaylightTime()) { 8385bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (Eas.USER_LOG) { 839bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, "TimeZone without DST found by offset: " + 8405bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank timeZone.getID()); 8415bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8425bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank return timeZone; 8435bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8445862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 8455bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // None found, return null 8465bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank return null; 8475862a85e17e81866ca82a9905577931947fbd44eMarc Blank } else { 848377230593dca7cb01483bfaf93959e5821f5f028Marc Blank TimeZoneDate dstStart = getTimeZoneDateFromSystemTime(timeZoneBytes, 8495862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET); 8505862a85e17e81866ca82a9905577931947fbd44eMarc Blank // See comment above for bias... 8515862a85e17e81866ca82a9905577931947fbd44eMarc Blank long dstSavings = 852377230593dca7cb01483bfaf93959e5821f5f028Marc Blank -1 * getLong(timeZoneBytes, MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET) * MINUTES; 8535862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8545862a85e17e81866ca82a9905577931947fbd44eMarc Blank // We'll go through each time zone to find one with the same DST transitions and 8555862a85e17e81866ca82a9905577931947fbd44eMarc Blank // savings length 8565862a85e17e81866ca82a9905577931947fbd44eMarc Blank for (String zoneId: zoneIds) { 8575862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Get the TimeZone using the zoneId 8585862a85e17e81866ca82a9905577931947fbd44eMarc Blank timeZone = TimeZone.getTimeZone(zoneId); 8595862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8605862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Our strategy here is to check just before and just after the transitions 8615862a85e17e81866ca82a9905577931947fbd44eMarc Blank // and see whether the check for daylight time matches the expectation 8625862a85e17e81866ca82a9905577931947fbd44eMarc Blank // If both transitions match, then we have a match for the offset and start/end 8635862a85e17e81866ca82a9905577931947fbd44eMarc Blank // of dst. That's the best we can do for now, since there's no other info 8645862a85e17e81866ca82a9905577931947fbd44eMarc Blank // provided by EAS (i.e. we can't get dynamic transitions, etc.) 8655862a85e17e81866ca82a9905577931947fbd44eMarc Blank 86679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Check one minute before and after DST start transition 86779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank long millisAtTransition = getMillisAtTimeZoneDateTransition(timeZone, dstStart); 8682c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank Date before = new Date(millisAtTransition - precision); 8692c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank Date after = new Date(millisAtTransition + precision); 8705862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (timeZone.inDaylightTime(before)) continue; 8715862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (!timeZone.inDaylightTime(after)) continue; 8725862a85e17e81866ca82a9905577931947fbd44eMarc Blank 87379268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Check one minute before and after DST end transition 87479268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank millisAtTransition = getMillisAtTimeZoneDateTransition(timeZone, dstEnd); 87579268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Note that we need to subtract an extra hour here, because we end up with 87679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // gaining an hour in the transition BACK to standard time 8772c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank before = new Date(millisAtTransition - (dstSavings + precision)); 8782c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank after = new Date(millisAtTransition + precision); 8795862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (!timeZone.inDaylightTime(before)) continue; 8805862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (timeZone.inDaylightTime(after)) continue; 8815862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8825862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Check that the savings are the same 8835862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dstSavings != timeZone.getDSTSavings()) continue; 88479268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank return timeZone; 8855862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 8862c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank boolean lenient = false; 887bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank boolean name = false; 8882c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank if ((dstStart.hour != dstEnd.hour) && (precision == STANDARD_DST_PRECISION)) { 8892c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank timeZone = tziStringToTimeZoneImpl(timeZoneString, LENIENT_DST_PRECISION); 8902c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank lenient = true; 8912c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank } else { 892bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank // We can't find a time zone match, so our last attempt is to see if there's 893bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank // a valid time zone name in the TZI; if not we'll just take the first TZ with 894bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank // a matching offset (which is likely wrong, but ... what else is there to do) 895bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank String tzName = getString(timeZoneBytes, MSFT_TIME_ZONE_STANDARD_NAME_OFFSET, 896bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank MSFT_TIME_ZONE_STRING_SIZE); 897bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank if (!tzName.isEmpty()) { 898bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank TimeZone tz = TimeZone.getTimeZone(tzName); 899bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank if (tz != null) { 900bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank timeZone = tz; 901bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank name = true; 902bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } else { 903bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank timeZone = TimeZone.getTimeZone(zoneIds[0]); 904bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } 905bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } else { 906bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank timeZone = TimeZone.getTimeZone(zoneIds[0]); 907bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank } 9082c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank } 909270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank if (Eas.USER_LOG) { 910bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, 9112c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank "No TimeZone with correct DST settings; using " + 912bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank (name ? "name" : (lenient ? "lenient" : "first")) + ": " + 913bcc7188e6244176cd8b3915af50e5c0034307ba4Marc Blank timeZone.getID()); 914270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank } 915270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return timeZone; 9165862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 9175862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 91879268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank return null; 9195862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 9205862a85e17e81866ca82a9905577931947fbd44eMarc Blank 9215c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank static public String convertEmailDateTimeToCalendarDateTime(String date) { 9225c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Format for email date strings is 2010-02-23T16:00:00.000Z 92379268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Format for calendar date strings is 20100223T160000Z 9245c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank return date.substring(0, 4) + date.substring(5, 7) + date.substring(8, 13) + 9255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank date.substring(14, 16) + date.substring(17, 19) + 'Z'; 9265c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 9275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 928377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static String formatTwo(int num) { 929377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (num <= 12) { 930377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return sTwoCharacterNumbers[num]; 931377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } else 932377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return Integer.toString(num); 933377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 93414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 935377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 936377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Generate an EAS formatted date/time string based on GMT. See below for details. 937377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 938377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static public String millisToEasDateTime(long millis) { 939c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank return millisToEasDateTime(millis, sGmtTimeZone, true); 940377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 9415862a85e17e81866ca82a9905577931947fbd44eMarc Blank 942377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 943e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * Generate a birthday string from a GregorianCalendar set appropriately; the format of this 944e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * string is YYYY-MM-DD 945e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * @param cal the calendar 946e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * @return the birthday string 947e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank */ 948e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank static public String calendarToBirthdayString(GregorianCalendar cal) { 949e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank StringBuilder sb = new StringBuilder(); 950e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append(cal.get(Calendar.YEAR)); 951e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append('-'); 952e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append(formatTwo(cal.get(Calendar.MONTH) + 1)); 953e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append('-'); 954e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH))); 955e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank return sb.toString(); 956e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } 957e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank 958e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank /** 959c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * Generate an EAS formatted local date/time string from a time and a time zone. If the final 960c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * argument is false, only a date will be returned (e.g. 20100331) 961377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param millis a time in milliseconds 962377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param tz a time zone 963c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * @param withTime if the time is to be included in the string 964c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * @return an EAS formatted string indicating the date (and time) in the given time zone 965377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 966c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank static public String millisToEasDateTime(long millis, TimeZone tz, boolean withTime) { 967377230593dca7cb01483bfaf93959e5821f5f028Marc Blank StringBuilder sb = new StringBuilder(); 968377230593dca7cb01483bfaf93959e5821f5f028Marc Blank GregorianCalendar cal = new GregorianCalendar(tz); 969377230593dca7cb01483bfaf93959e5821f5f028Marc Blank cal.setTimeInMillis(millis); 970377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sb.append(cal.get(Calendar.YEAR)); 971377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sb.append(formatTwo(cal.get(Calendar.MONTH) + 1)); 972377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH))); 973c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (withTime) { 974c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append('T'); 975c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append(formatTwo(cal.get(Calendar.HOUR_OF_DAY))); 976c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append(formatTwo(cal.get(Calendar.MINUTE))); 977c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append(formatTwo(cal.get(Calendar.SECOND))); 978c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (tz == sGmtTimeZone) { 979c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append('Z'); 980c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank } 981820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 982820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return sb.toString(); 983820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 984820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 985820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 98610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Return the true minute at which a transition occurs 98710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Our transition time should be the in the minute BEFORE the transition 98810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * If this minute is 59, set minute to 0 and increment the hour 98910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * NOTE: We don't want to add a minute and retrieve minute/hour from the Calendar, because 99010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Calendar time will itself be influenced by the transition! So adding 1 minute to 99110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * 01:59 (assume PST->PDT) will become 03:00, which isn't what we want (we want 02:00) 99210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * 99310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @param calendar the calendar holding the transition date/time 99410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @return the true minute of the transition 99510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank */ 99679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static int getTrueTransitionMinute(GregorianCalendar calendar) { 99710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank int minute = calendar.get(Calendar.MINUTE); 99810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank if (minute == 59) { 99910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank minute = 0; 100010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 100110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank return minute; 100210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 100310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank 100410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank /** 100510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Return the true hour at which a transition occurs 100610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * See description for getTrueTransitionMinute, above 100710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @param calendar the calendar holding the transition date/time 100810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @return the true hour of the transition 100910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank */ 101079268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static int getTrueTransitionHour(GregorianCalendar calendar) { 101110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank int hour = calendar.get(Calendar.HOUR_OF_DAY); 101210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank hour++; 101310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank if (hour == 24) { 101410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank hour = 0; 101510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 101610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank return hour; 101710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 101810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank 101910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank /** 102010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Generate a date/time string suitable for VTIMEZONE from a transition time in millis 102110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * The format is YYYYMMDDTHHMMSS 102210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @param millis a transition time in milliseconds 1023820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz a time zone 1024820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param dst whether we're entering daylight time 1025820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 102679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static String transitionMillisToVCalendarTime(long millis, TimeZone tz, boolean dst) { 1027820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank StringBuilder sb = new StringBuilder(); 1028820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar cal = new GregorianCalendar(tz); 1029820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal.setTimeInMillis(millis); 1030820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(cal.get(Calendar.YEAR)); 1031820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(formatTwo(cal.get(Calendar.MONTH) + 1)); 1032820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH))); 1033820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('T'); 103410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank sb.append(formatTwo(getTrueTransitionHour(cal))); 103510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank sb.append(formatTwo(getTrueTransitionMinute(cal))); 103610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank sb.append(formatTwo(0)); 1037377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return sb.toString(); 1038377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 10395862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1040cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank /** 1041270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * Returns a UTC calendar with year/month/day from local calendar and h/m/s/ms = 0 1042270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @param time the time in seconds of an all-day event in local time 1043270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @return the time in seconds in UTC 1044cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank */ 1045270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank static public long getUtcAllDayCalendarTime(long time, TimeZone localTimeZone) { 1046270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return transposeAllDayTime(time, localTimeZone, UTC_TIMEZONE); 1047270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank } 1048270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank 1049270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank /** 1050270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * Returns a local calendar with year/month/day from UTC calendar and h/m/s/ms = 0 1051270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @param time the time in seconds of an all-day event in UTC 1052270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @return the time in seconds in local time 1053270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank */ 1054270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank static public long getLocalAllDayCalendarTime(long time, TimeZone localTimeZone) { 1055270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return transposeAllDayTime(time, UTC_TIMEZONE, localTimeZone); 1056270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank } 1057270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank 1058270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank static private long transposeAllDayTime(long time, TimeZone fromTimeZone, 1059270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank TimeZone toTimeZone) { 1060270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank GregorianCalendar fromCalendar = new GregorianCalendar(fromTimeZone); 1061270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank fromCalendar.setTimeInMillis(time); 1062270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank GregorianCalendar toCalendar = new GregorianCalendar(toTimeZone); 1063cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank // Set this calendar with correct year, month, and day, but zero hour, minute, and seconds 1064270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank toCalendar.set(fromCalendar.get(GregorianCalendar.YEAR), 1065270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank fromCalendar.get(GregorianCalendar.MONTH), 1066270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank fromCalendar.get(GregorianCalendar.DATE), 0, 0, 0); 10673baaee079e644467cf18c9b250ac30485f9c54e0Marc Blank toCalendar.set(GregorianCalendar.MILLISECOND, 0); 1068270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return toCalendar.getTimeInMillis(); 1069cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank } 1070cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank 1071377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static void addByDay(StringBuilder rrule, int dow, int wom) { 10725862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";BYDAY="); 10735862a85e17e81866ca82a9905577931947fbd44eMarc Blank boolean addComma = false; 10745862a85e17e81866ca82a9905577931947fbd44eMarc Blank for (int i = 0; i < 7; i++) { 10755862a85e17e81866ca82a9905577931947fbd44eMarc Blank if ((dow & 1) == 1) { 10765862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (addComma) { 10775862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(','); 10785862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10795862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (wom > 0) { 10805862a85e17e81866ca82a9905577931947fbd44eMarc Blank // 5 = last week -> -1 10815862a85e17e81866ca82a9905577931947fbd44eMarc Blank // So -1SU = last sunday 10825862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(wom == 5 ? -1 : wom); 10835862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10845862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(sDayTokens[i]); 10855862a85e17e81866ca82a9905577931947fbd44eMarc Blank addComma = true; 10865862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10875862a85e17e81866ca82a9905577931947fbd44eMarc Blank dow >>= 1; 10885862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10895862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10905862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1091f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank static void addBySetpos(StringBuilder rrule, int dow, int wom) { 1092f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Indicate the days, but don't use wom in this case (it's used in the BYSETPOS); 1093f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addByDay(rrule, dow, 0); 1094f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank rrule.append(";BYSETPOS="); 1095f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank rrule.append(wom == 5 ? "-1" : wom); 1096f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1097f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank 10985862a85e17e81866ca82a9905577931947fbd44eMarc Blank static void addByMonthDay(StringBuilder rrule, int dom) { 10995862a85e17e81866ca82a9905577931947fbd44eMarc Blank // 127 means last day of the month 11005862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dom == 127) { 11015862a85e17e81866ca82a9905577931947fbd44eMarc Blank dom = -1; 11025862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 11035862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";BYMONTHDAY=" + dom); 11045862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 11055862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1106f35b67cef20189c12a1a387dedf200eb30089725Marc Blank /** 1107f35b67cef20189c12a1a387dedf200eb30089725Marc Blank * Generate the String version of the EAS integer for a given BYDAY value in an rrule 1108f35b67cef20189c12a1a387dedf200eb30089725Marc Blank * @param dow the BYDAY value of the rrule 1109f35b67cef20189c12a1a387dedf200eb30089725Marc Blank * @return the String version of the EAS value of these days 1110f35b67cef20189c12a1a387dedf200eb30089725Marc Blank */ 111114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank static String generateEasDayOfWeek(String dow) { 1112f35b67cef20189c12a1a387dedf200eb30089725Marc Blank int bits = 0; 111314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int bit = 1; 111414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank for (String token: sDayTokens) { 1115f35b67cef20189c12a1a387dedf200eb30089725Marc Blank // If we can find the day in the dow String, add the bit to our bits value 1116f35b67cef20189c12a1a387dedf200eb30089725Marc Blank if (dow.indexOf(token) >= 0) { 1117f35b67cef20189c12a1a387dedf200eb30089725Marc Blank bits |= bit; 111814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1119f35b67cef20189c12a1a387dedf200eb30089725Marc Blank bit <<= 1; 112014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1121f35b67cef20189c12a1a387dedf200eb30089725Marc Blank return Integer.toString(bits); 112214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 112314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 1124377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 1125377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Extract the value of a token in an RRULE string 1126377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param rrule an RRULE string 1127377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param token a token to look for in the RRULE 1128377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return the value of that token 1129377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 113014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank static String tokenFromRrule(String rrule, String token) { 113114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int start = rrule.indexOf(token); 113214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (start < 0) return null; 113314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int len = rrule.length(); 113414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank start += token.length(); 113514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int end = start; 113614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank char c; 113714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank do { 113814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank c = rrule.charAt(end++); 1139b129a5f1b340ae6362397685c407099ceae8d9e9Marc Blank if ((c == ';') || (end == len)) { 114014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (end == len) end++; 114114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank return rrule.substring(start, end -1); 114214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1143377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } while (true); 114414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 114514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 114614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank /** 114721c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank * Reformat an RRULE style UNTIL to an EAS style until 114821c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank */ 114960df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank @VisibleForTesting 11502f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shrauner static String recurrenceUntilToEasUntil(String until) throws ParseException { 115160df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // Get a calendar in our local time zone 115260df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank GregorianCalendar localCalendar = new GregorianCalendar(TimeZone.getDefault()); 115360df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // Set the time per GMT time in the 'until' 115460df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank localCalendar.setTimeInMillis(Utility.parseDateTimeToMillis(until)); 115521c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank StringBuilder sb = new StringBuilder(); 115660df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // Build a string with local year/month/date 115760df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank sb.append(localCalendar.get(Calendar.YEAR)); 115860df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank sb.append(formatTwo(localCalendar.get(Calendar.MONTH) + 1)); 115960df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank sb.append(formatTwo(localCalendar.get(Calendar.DAY_OF_MONTH))); 116060df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // EAS ignores the time in 'until'; go figure 1161e3e9ef55a2afd2c121e1f214a8fa34fced3bac38Marc Blank sb.append("T000000Z"); 116221c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank return sb.toString(); 116321c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank } 116421c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank 116521c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank /** 1166f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank * Convenience method to add "count", "interval", and "until" to an EAS calendar stream 1167f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank * According to EAS docs, OCCURRENCES must always come before INTERVAL 116821c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank */ 1169f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank static private void addCountIntervalAndUntil(String rrule, Serializer s) throws IOException { 1170f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String count = tokenFromRrule(rrule, "COUNT="); 1171f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (count != null) { 1172f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_OCCURRENCES, count); 1173f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1174f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String interval = tokenFromRrule(rrule, "INTERVAL="); 1175f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (interval != null) { 1176f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_INTERVAL, interval); 1177f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 117821c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank String until = tokenFromRrule(rrule, "UNTIL="); 117921c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank if (until != null) { 11802f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shrauner try { 11812f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shrauner s.data(Tags.CALENDAR_RECURRENCE_UNTIL, recurrenceUntilToEasUntil(until)); 11822f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shrauner } catch (ParseException e) { 11832f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shrauner LogUtils.w(TAG, "Parse error for CALENDAR_RECURRENCE_UNTIL tag.", e); 11842f369a47e14916a34f49c79c0a246a2e3ac3072fJay Shrauner } 118521c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank } 118621c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank } 118721c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank 1188f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank static private void addByDay(String byDay, Serializer s) throws IOException { 1189f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank // This can be 1WE (1st Wednesday) or -1FR (last Friday) 1190f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank int weekOfMonth = byDay.charAt(0); 1191f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String bareByDay; 1192f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (weekOfMonth == '-') { 1193f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank // -1 is the only legal case (last week) Use "5" for EAS 1194f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank weekOfMonth = 5; 1195f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank bareByDay = byDay.substring(2); 1196f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } else { 1197f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank weekOfMonth = weekOfMonth - '0'; 1198f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank bareByDay = byDay.substring(1); 1199f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1200f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, Integer.toString(weekOfMonth)); 1201f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(bareByDay)); 1202f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1203f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank 1204f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank static private void addByDaySetpos(String byDay, String bySetpos, Serializer s) 1205f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank throws IOException { 1206f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank int weekOfMonth = bySetpos.charAt(0); 1207f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (weekOfMonth == '-') { 1208f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // -1 is the only legal case (last week) Use "5" for EAS 1209f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank weekOfMonth = 5; 1210f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1211f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank weekOfMonth = weekOfMonth - '0'; 1212f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1213f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, Integer.toString(weekOfMonth)); 1214f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(byDay)); 1215f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1216f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank 121721c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank /** 121814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * Write recurrence information to EAS based on the RRULE in CalendarProvider 1219d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert * 122014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @param rrule the RRULE, from CalendarProvider 122114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @param startTime, the DTSTART of this Event 1222d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert * @param timeZone the time zone of the Event 122314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @param s the Serializer we're using to write WBXML data 1224d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert * 122514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @throws IOException 122614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank */ 122714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // NOTE: For the moment, we're only parsing recurrence types that are supported by the 1228c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank // Calendar app UI, which is a subset of possible recurrence types 122914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // This code must be updated when the Calendar adds new functionality 1230d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert static public void recurrenceFromRrule(String rrule, long startTime, TimeZone timeZone, 1231d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert Serializer s) 123279268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank throws IOException { 123388683d3ebf9720ee648220dd6884e200d5e87038Marc Blank if (Eas.USER_LOG) { 1234bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon LogUtils.d(TAG, "RRULE: " + rrule); 123588683d3ebf9720ee648220dd6884e200d5e87038Marc Blank } 1236d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final String freq = tokenFromRrule(rrule, "FREQ="); 123714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // If there's no FREQ=X, then we don't write a recurrence 123814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // Note that we duplicate s.start(Tags.CALENDAR_RECURRENCE); s.end(); to prevent the 123914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // possibility of writing out a partial recurrence stanza 124014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (freq != null) { 124114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (freq.equals("DAILY")) { 124214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 124314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "0"); 1244f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 124514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 124614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else if (freq.equals("WEEKLY")) { 124714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 124814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "1"); 124914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // Requires a day of week (whereas RRULE does not) 1250f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 1251d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final String byDay = tokenFromRrule(rrule, "BYDAY="); 125214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (byDay != null) { 125314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(byDay)); 1254f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Find week number (1-4 and 5 for last) 1255f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (byDay.startsWith("-1")) { 1256f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, "5"); 1257f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1258d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final char c = byDay.charAt(0); 1259f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (c >= '1' && c <= '4') { 1260f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, byDay.substring(0, 1)); 1261f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1262f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 126314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 126414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 126514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else if (freq.equals("MONTHLY")) { 126614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byMonthDay = tokenFromRrule(rrule, "BYMONTHDAY="); 126714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (byMonthDay != null) { 126814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 1269f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Special case for last day of month 1270d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert if (byMonthDay.equals("-1")) { 1271f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "3"); 1272f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addCountIntervalAndUntil(rrule, s); 1273f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, "127"); 1274f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1275f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // The nth day of the month 1276f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "2"); 1277f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addCountIntervalAndUntil(rrule, s); 1278f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay); 1279f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 128014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 128114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else { 1282d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final String byDay = tokenFromRrule(rrule, "BYDAY="); 1283d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final String bySetpos = tokenFromRrule(rrule, "BYSETPOS="); 128414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (byDay != null) { 128514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 128614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "3"); 1287f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 1288f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (bySetpos != null) { 1289f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addByDaySetpos(byDay, bySetpos, s); 1290f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1291f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addByDay(byDay, s); 1292f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 129314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 1294d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert } else { 1295d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert // Neither BYDAY or BYMONTHDAY implies it's BYMONTHDAY based on DTSTART 1296d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert // Calculate the day from the startDate 1297d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert s.start(Tags.CALENDAR_RECURRENCE); 1298d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final GregorianCalendar cal = new GregorianCalendar(); 1299d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert cal.setTimeInMillis(startTime); 1300d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert cal.setTimeZone(timeZone); 1301d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert byMonthDay = Integer.toString(cal.get(Calendar.DAY_OF_MONTH)); 1302d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert s.data(Tags.CALENDAR_RECURRENCE_TYPE, "2"); 1303d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert addCountIntervalAndUntil(rrule, s); 1304d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay); 1305d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert s.end(); 130614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 130714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 130814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else if (freq.equals("YEARLY")) { 130914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byMonth = tokenFromRrule(rrule, "BYMONTH="); 131014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byMonthDay = tokenFromRrule(rrule, "BYMONTHDAY="); 1311d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final String byDay = tokenFromRrule(rrule, "BYDAY="); 1312f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (byMonth == null && byMonthDay == null) { 131314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // Calculate the month and day from the startDate 1314d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert final GregorianCalendar cal = new GregorianCalendar(); 131514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank cal.setTimeInMillis(startTime); 1316d9cfcc6c265974e9bcff93fc4541b402afdfa116Alon Albert cal.setTimeZone(timeZone); 131714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank byMonth = Integer.toString(cal.get(Calendar.MONTH) + 1); 131814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank byMonthDay = Integer.toString(cal.get(Calendar.DAY_OF_MONTH)); 131914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1320f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (byMonth != null && (byMonthDay != null || byDay != null)) { 1321f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.start(Tags.CALENDAR_RECURRENCE); 1322f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, byDay == null ? "5" : "6"); 1323f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 1324f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_MONTHOFYEAR, byMonth); 1325f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank // Note that both byMonthDay and byDay can't be true in a valid RRULE 1326f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (byMonthDay != null) { 1327f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay); 1328f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } else { 1329f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addByDay(byDay, s); 1330f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1331f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.end(); 1332f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1333377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 133414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 133514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 133614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 1337377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 1338377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Build an RRULE String from EAS recurrence information 1339377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param type the type of recurrence 1340377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param occurrences how many recurrences (instances) 1341377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param interval the interval between recurrences 1342377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param dow day of the week 1343377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param dom day of the month 1344377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param wom week of the month 1345377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param moy month of the year 1346377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param until the last recurrence time 1347377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return a valid RRULE String 1348377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 13495862a85e17e81866ca82a9905577931947fbd44eMarc Blank static public String rruleFromRecurrence(int type, int occurrences, int interval, int dow, 13505862a85e17e81866ca82a9905577931947fbd44eMarc Blank int dom, int wom, int moy, String until) { 13510c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner if (type < 0 || type >= sTypeToFreq.length) { 13520c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner return null; 13530c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner } 13540c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner final String typeStr = sTypeToFreq[type]; 13550c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner // Type array is sparse (eg, no type 4), so catch invalid (empty) types 13560c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner if (TextUtils.isEmpty(typeStr)) { 13570c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner return null; 13580c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner } 13590c49fe537fed155ecf0df57065255a738f69e193Jay Shrauner StringBuilder rrule = new StringBuilder("FREQ=" + typeStr); 13605862a85e17e81866ca82a9905577931947fbd44eMarc Blank // INTERVAL and COUNT 13615862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (occurrences > 0) { 13625862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";COUNT=" + occurrences); 13635862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 1364f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (interval > 0) { 1365f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank rrule.append(";INTERVAL=" + interval); 1366f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 13675862a85e17e81866ca82a9905577931947fbd44eMarc Blank 13685862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Days, weeks, months, etc. 13695862a85e17e81866ca82a9905577931947fbd44eMarc Blank switch(type) { 13705862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 0: // DAILY 13715862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 1: // WEEKLY 1372f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (dow > 0) addByDay(rrule, dow, wom); 13735862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13745862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 2: // MONTHLY 13755862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dom > 0) addByMonthDay(rrule, dom); 13765862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13775862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 3: // MONTHLY (on the nth day) 1378f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // 127 is a special case meaning "last day of the month" 1379f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (dow == 127) { 1380f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank rrule.append(";BYMONTHDAY=-1"); 1381f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // week 5 and dow = weekdays -> last weekday (need BYSETPOS) 1382e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } else if ((wom == 5 || wom == 1) && (dow == EAS_WEEKDAYS || dow == EAS_WEEKENDS)) { 1383f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addBySetpos(rrule, dow, wom); 1384f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else if (dow > 0) addByDay(rrule, dow, wom); 13855862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 1386820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank case 5: // YEARLY (specific day) 13875862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dom > 0) addByMonthDay(rrule, dom); 13885862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (moy > 0) { 13895862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";BYMONTH=" + moy); 13905862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 13915862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 1392820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank case 6: // YEARLY 13935862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dow > 0) addByDay(rrule, dow, wom); 1394820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dom > 0) addByMonthDay(rrule, dom); 1395820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (moy > 0) { 1396820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank rrule.append(";BYMONTH=" + moy); 1397820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 13985862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13995862a85e17e81866ca82a9905577931947fbd44eMarc Blank default: 14005862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 14015862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 14025862a85e17e81866ca82a9905577931947fbd44eMarc Blank 14035862a85e17e81866ca82a9905577931947fbd44eMarc Blank // UNTIL comes last 14045862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (until != null) { 140521c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank rrule.append(";UNTIL=" + until); 14065862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 14075862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1408f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (Eas.USER_LOG) { 1409942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy LogUtils.d(Logging.LOG_TAG, "Created rrule: " + rrule); 1410f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 14115862a85e17e81866ca82a9905577931947fbd44eMarc Blank return rrule.toString(); 14125862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 141377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank 141477110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank /** 141577110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * Create a Calendar in CalendarProvider to which synced Events will be linked 14166e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu * @param context 14176e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu * @param contentResolver 141877110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @param account the account being synced 141977110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @param mailbox the Exchange mailbox for the calendar 142077110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @return the unique id of the Calendar 142177110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank */ 14226e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu static public long createCalendar(final Context context, final ContentResolver contentResolver, 14236e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu final Account account, final Mailbox mailbox) { 142477110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // Create a Calendar object 142577110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank ContentValues cv = new ContentValues(); 142677110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // TODO How will this change if the user changes his account display name? 14271424b228e105b1c5c8b54eb0f401a549161f8f5fAlon Albert cv.put(Calendars.CALENDAR_DISPLAY_NAME, mailbox.mDisplayName); 14289e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik cv.put(Calendars.ACCOUNT_NAME, account.mEmailAddress); 14299e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik cv.put(Calendars.ACCOUNT_TYPE, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE); 143077110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank cv.put(Calendars.SYNC_EVENTS, 1); 14311424b228e105b1c5c8b54eb0f401a549161f8f5fAlon Albert cv.put(Calendars._SYNC_ID, mailbox.mServerId); 1432443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.VISIBLE, 1); 14335a02f79c91df6df44f3c95742f61f2c25c464cc3Marc Blank // Don't show attendee status if we're the organizer 1434443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.CAN_ORGANIZER_RESPOND, 0); 1435443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.CAN_MODIFY_TIME_ZONE, 0); 1436443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.MAX_REMINDERS, 1); 143780a8e57ce2dc2695ed6f35599d326090e4ad9faeRoboErik cv.put(Calendars.ALLOWED_REMINDERS, ALLOWED_REMINDER_TYPES); 1438937af5abcbc1268f22a3058b00835c74ba20f116RoboErik cv.put(Calendars.ALLOWED_ATTENDEE_TYPES, ALLOWED_ATTENDEE_TYPES); 1439937af5abcbc1268f22a3058b00835c74ba20f116RoboErik cv.put(Calendars.ALLOWED_AVAILABILITY, ALLOWED_AVAILABILITIES); 14405a02f79c91df6df44f3c95742f61f2c25c464cc3Marc Blank 144177110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // TODO Coordinate account colors w/ Calendar, if possible 14426e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu int color = new AccountServiceProxy(context).getAccountColor(account.mId); 14439e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik cv.put(Calendars.CALENDAR_COLOR, color); 144404c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik cv.put(Calendars.CALENDAR_TIME_ZONE, Time.getCurrentTimezone()); 144504c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik cv.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER); 144677110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank cv.put(Calendars.OWNER_ACCOUNT, account.mEmailAddress); 144777110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank 14486e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu Uri uri = contentResolver.insert(asSyncAdapter(Calendars.CONTENT_URI, account.mEmailAddress, 14496989716b639d274a98141674556ac9402be32ebeRoboErik Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), cv); 145077110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // We save the id of the calendar into mSyncStatus 145177110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank if (uri != null) { 145277110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank String stringId = uri.getPathSegments().get(1); 145377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank mailbox.mSyncStatus = stringId; 145477110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank return Long.parseLong(stringId); 145577110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank } 145677110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank return -1; 145777110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank } 1458c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 14596989716b639d274a98141674556ac9402be32ebeRoboErik static Uri asSyncAdapter(Uri uri, String account, String accountType) { 14606989716b639d274a98141674556ac9402be32ebeRoboErik return uri.buildUpon() 1461693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, 1462693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik "true") 14636989716b639d274a98141674556ac9402be32ebeRoboErik .appendQueryParameter(Calendars.ACCOUNT_NAME, account) 14646989716b639d274a98141674556ac9402be32ebeRoboErik .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); 14656989716b639d274a98141674556ac9402be32ebeRoboErik } 14666989716b639d274a98141674556ac9402be32ebeRoboErik 1467b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank /** 1468b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank * Return the uid for an event based on its globalObjId 1469b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank * @param globalObjId the base64 encoded String provided by EAS 1470b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank * @return the uid for the calendar event 1471b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank */ 1472b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank static public String getUidFromGlobalObjId(String globalObjId) { 1473b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank StringBuilder sb = new StringBuilder(); 1474b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // First get the decoded base64 1475b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank try { 1476b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank byte[] idBytes = Base64.decode(globalObjId, Base64.DEFAULT); 1477b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank String idString = new String(idBytes); 1478b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // If the base64 decoded string contains the magic substring: "vCal-Uid", then 1479b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // the actual uid is hidden within; the magic substring is never at the start of the 1480b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // decoded base64 1481b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank int index = idString.indexOf("vCal-Uid"); 1482b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank if (index > 0) { 1483b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // The uid starts after "vCal-Uidxxxx", where xxxx are padding 1484b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // characters. And it ends before the last character, which is ascii 0 1485b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank return idString.substring(index + 12, idString.length() - 1); 1486b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } else { 1487b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // This is an EAS uid. Go through the bytes and write out the hex 1488b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // values as characters; this is what we'll need to pass back to EAS 1489b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // when responding to the invitation 1490b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank for (byte b: idBytes) { 1491b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank Utility.byteToHex(sb, b); 1492b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1493b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank return sb.toString(); 1494b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1495b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } catch (RuntimeException e) { 1496b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // In the worst of cases (bad format, etc.), we can always return the input 1497b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank return globalObjId; 1498b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1499b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1500b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank 1501dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank /** 1502dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * Get a selfAttendeeStatus from a busy status 1503dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * The default here is NONE (i.e. we don't know the status) 1504dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * Note that a busy status of FREE must mean NONE as well, since it can't mean declined 1505dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * (there would be no event) 1506dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @param busyStatus the busy status, from EAS 1507dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @return the corresponding value for selfAttendeeStatus 1508dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank */ 1509edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank static public int attendeeStatusFromBusyStatus(int busyStatus) { 1510edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank int attendeeStatus; 1511dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank switch (busyStatus) { 1512dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case BUSY_STATUS_BUSY: 1513edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; 1514dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1515dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case BUSY_STATUS_TENTATIVE: 1516edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; 1517dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1518dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case BUSY_STATUS_FREE: 1519e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank case BUSY_STATUS_OUT_OF_OFFICE: 1520dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank default: 1521edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; 1522dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1523edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank return attendeeStatus; 1524dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1525dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank 152665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank /** 1527f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank * Get a selfAttendeeStatus from a response type (EAS 14+) 1528f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank * The default here is NONE (i.e. we don't know the status), though in theory this can't happen 1529f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank * @param busyStatus the response status, from EAS 153065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank * @return the corresponding value for selfAttendeeStatus 153165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank */ 153265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank static public int attendeeStatusFromResponseType(int responseType) { 153365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank int attendeeStatus; 153465d022dc43e4461e86fd7bc143591f542b07428bMarc Blank switch (responseType) { 153565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_NOT_RESPONDED: 15361bbcf25030104af1631ae74b3e1c3033e09ea4edAlon Albert attendeeStatus = Attendees.ATTENDEE_STATUS_INVITED; 153765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 153865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_ACCEPTED: 153965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; 154065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 154165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_TENTATIVE: 154265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; 154365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 154465d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_DECLINED: 154565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_DECLINED; 154665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 154765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank default: 154865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; 154965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank } 155065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank return attendeeStatus; 155165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank } 155265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank 1553dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank /** Get a busy status from a selfAttendeeStatus 1554dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * The default here is BUSY 1555dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @param selfAttendeeStatus from CalendarProvider2 1556dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @return the corresponding value of busy status 1557dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank */ 1558edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank static public int busyStatusFromAttendeeStatus(int selfAttendeeStatus) { 1559dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank int busyStatus; 1560dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank switch (selfAttendeeStatus) { 1561dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_DECLINED: 1562dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_NONE: 1563dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_INVITED: 1564dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank busyStatus = BUSY_STATUS_FREE; 1565dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1566dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_TENTATIVE: 1567dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank busyStatus = BUSY_STATUS_TENTATIVE; 1568dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1569dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_ACCEPTED: 1570dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank default: 1571dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank busyStatus = BUSY_STATUS_BUSY; 1572dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1573dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1574dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank return busyStatus; 1575dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1576dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank 1577e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank /** Get a busy status from event availability 1578e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * The default here is TENTATIVE 1579e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @param availability from CalendarProvider2 1580e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @return the corresponding value of busy status 1581e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank */ 1582e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank static public int busyStatusFromAvailability(int availability) { 1583e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank int busyStatus; 1584e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank switch (availability) { 1585e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case Events.AVAILABILITY_BUSY: 1586e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank busyStatus = BUSY_STATUS_BUSY; 1587e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1588e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case Events.AVAILABILITY_FREE: 1589e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank busyStatus = BUSY_STATUS_FREE; 1590e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1591e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case Events.AVAILABILITY_TENTATIVE: 1592e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank default: 1593e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank busyStatus = BUSY_STATUS_TENTATIVE; 1594e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1595e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1596e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank return busyStatus; 1597e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1598e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank 1599e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank /** Get an event availability from busy status 1600e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * The default here is TENTATIVE 1601e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @param busyStatus from CalendarProvider2 1602e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @return the corresponding availability value 1603e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank */ 1604e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank static public int availabilityFromBusyStatus(int busyStatus) { 1605e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank int availability; 1606e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank switch (busyStatus) { 1607e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case BUSY_STATUS_BUSY: 1608e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank availability = Events.AVAILABILITY_BUSY; 1609e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1610e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case BUSY_STATUS_FREE: 1611e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank availability = Events.AVAILABILITY_FREE; 1612e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1613e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case BUSY_STATUS_TENTATIVE: 1614e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank default: 1615e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank availability = Events.AVAILABILITY_TENTATIVE; 1616e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1617e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1618e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank return availability; 1619e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1620e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank 1621dafc866120dac68fabbddcc9943e3995894c5244Marc Blank static public String buildMessageTextFromEntityValues(Context context, 1622dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ContentValues entityValues, StringBuilder sb) { 1623dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (sb == null) { 1624dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb = new StringBuilder(); 1625dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1626dafc866120dac68fabbddcc9943e3995894c5244Marc Blank Resources resources = context.getResources(); 1627dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // TODO: Add more detail to message text 1628dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // Right now, we're using.. When: Tuesday, March 5th at 2:00pm 1629dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // What we're missing is the duration and any recurrence information. So this should be 1630dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // more like... When: Tuesdays, starting March 5th from 2:00pm - 3:00pm 1631dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // This would require code to build complex strings, and it will have to wait 16329ca8918b82221dd441293973ffb84d565a52993aMarc Blank // For now, we'll just use the meeting_recurring string 1633f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank 1634f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank boolean allDayEvent = false; 1635f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank if (entityValues.containsKey(Events.ALL_DAY)) { 1636f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank Integer ade = entityValues.getAsInteger(Events.ALL_DAY); 1637f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank allDayEvent = (ade != null) && (ade == 1); 1638f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank } 1639f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank boolean recurringEvent = !entityValues.containsKey(Events.ORIGINAL_SYNC_ID) && 1640f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank entityValues.containsKey(Events.RRULE); 1641f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank 1642a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner if (entityValues.containsKey(Events.DTSTART)) { 1643a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner final String dateTimeString; 1644a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner final int res; 1645a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner final long startTime = entityValues.getAsLong(Events.DTSTART); 1646a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner if (allDayEvent) { 1647a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner final Date date = new Date(getLocalAllDayCalendarTime(startTime, 1648a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner TimeZone.getDefault())); 1649a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner dateTimeString = DateFormat.getDateInstance().format(date); 1650a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner res = recurringEvent ? R.string.meeting_allday_recurring 1651a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner : R.string.meeting_allday; 1652a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner } else { 1653a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner dateTimeString = DateFormat.getDateTimeInstance().format( 1654a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner new Date(startTime)); 1655a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner res = recurringEvent ? R.string.meeting_recurring 1656a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner : R.string.meeting_when; 1657a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner } 1658a53b7d0cefc73c63ac83708dfc4b554bdf01b1b6Jay Shrauner sb.append(resources.getString(res, dateTimeString)); 16599ca8918b82221dd441293973ffb84d565a52993aMarc Blank } 1660f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank 1661dafc866120dac68fabbddcc9943e3995894c5244Marc Blank String location = null; 1662dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (entityValues.containsKey(Events.EVENT_LOCATION)) { 1663dafc866120dac68fabbddcc9943e3995894c5244Marc Blank location = entityValues.getAsString(Events.EVENT_LOCATION); 1664efae936b117c9e4f3056d52fdbfe4d3f261483e5Marc Blank if (!TextUtils.isEmpty(location)) { 1665dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append("\n"); 1666dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append(resources.getString(R.string.meeting_where, location)); 1667dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1668dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1669dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // If there's a description for this event, append it 1670dafc866120dac68fabbddcc9943e3995894c5244Marc Blank String desc = entityValues.getAsString(Events.DESCRIPTION); 1671dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (desc != null) { 1672dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append("\n--\n"); 1673dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append(desc); 1674dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1675dafc866120dac68fabbddcc9943e3995894c5244Marc Blank return sb.toString(); 1676dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1677dafc866120dac68fabbddcc9943e3995894c5244Marc Blank 1678c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank /** 167924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * Add an attendee to the ics attachment and the to list of the Message being composed 168024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param ics the ics attachment writer 168124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param toList the list of addressees for this email 168224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param attendeeName the name of the attendee 168324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param attendeeEmail the email address of the attendee 168424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param messageFlag the flag indicating the action to be indicated by the message 168524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param account the sending account of the email 168624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank */ 168724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank static private void addAttendeeToMessage(SimpleIcsWriter ics, ArrayList<Address> toList, 168824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank String attendeeName, String attendeeEmail, int messageFlag, Account account) { 168924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if ((messageFlag & Message.FLAG_OUTGOING_MEETING_REQUEST_MASK) != 0) { 169024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank String icalTag = ICALENDAR_ATTENDEE_INVITE; 169124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if ((messageFlag & Message.FLAG_OUTGOING_MEETING_CANCEL) != 0) { 169224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_CANCEL; 169324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 169424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (attendeeName != null) { 169524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag += ";CN=" + SimpleIcsWriter.quoteParamValue(attendeeName); 169624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 169724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank ics.writeTag(icalTag, "MAILTO:" + attendeeEmail); 169824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank toList.add(attendeeName == null ? new Address(attendeeEmail) : 169924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank new Address(attendeeEmail, attendeeName)); 170024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } else if (attendeeEmail.equalsIgnoreCase(account.mEmailAddress)) { 170124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank String icalTag = null; 170224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank switch (messageFlag) { 170324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank case Message.FLAG_OUTGOING_MEETING_ACCEPT: 170424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_ACCEPT; 170524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank break; 170624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank case Message.FLAG_OUTGOING_MEETING_DECLINE: 170724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_DECLINE; 170824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank break; 170924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank case Message.FLAG_OUTGOING_MEETING_TENTATIVE: 171024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_TENTATIVE; 171124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank break; 171224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 171324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (icalTag != null) { 171424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (attendeeName != null) { 171524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag += ";CN=" 171624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank + SimpleIcsWriter.quoteParamValue(attendeeName); 171724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 171824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank ics.writeTag(icalTag, "MAILTO:" + attendeeEmail); 171924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 172024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 172124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 172224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 172324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank /** 1724c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * Create a Message for an (Event) Entity 1725c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param entity the Entity for the Event (as might be retrieved by CalendarProvider) 1726c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param messageFlag the Message.FLAG_XXX constant indicating the type of email to be sent 1727275e73703e09a1211ae6aa6fc2a43226e1fcdeedMartin Hibdon * @param uid the unique id of this Event, or null if it can be retrieved from the Event 1728275e73703e09a1211ae6aa6fc2a43226e1fcdeedMartin Hibdon * @param account the user's account 1729c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @return a Message with many fields pre-filled (more later) 1730c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank */ 1731c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank static public Message createMessageForEntity(Context context, Entity entity, 17325c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank int messageFlag, String uid, Account account) { 1733601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank return createMessageForEntity(context, entity, messageFlag, uid, account, 173424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank null /*specifiedAttendee*/); 1735601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank } 1736601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank 1737601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank static public EmailContent.Message createMessageForEntity(Context context, Entity entity, 173824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank int messageFlag, String uid, Account account, String specifiedAttendee) { 1739c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank ContentValues entityValues = entity.getEntityValues(); 17405c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ArrayList<NamedContentValues> subValues = entity.getSubValues(); 174104e4cdadbe23670fd99d21ffb88af40ef77d69aeJay Shrauner boolean isException = entityValues.containsKey(Events.ORIGINAL_INSTANCE_TIME); 1742f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank boolean isReply = false; 1743c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1744c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank EmailContent.Message msg = new EmailContent.Message(); 1745c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank msg.mFlags = messageFlag; 1746c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank msg.mTimeStamp = System.currentTimeMillis(); 1747c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1748c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank String method; 1749f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if ((messageFlag & EmailContent.Message.FLAG_OUTGOING_MEETING_INVITE) != 0) { 1750c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank method = "REQUEST"; 1751f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank } else if ((messageFlag & EmailContent.Message.FLAG_OUTGOING_MEETING_CANCEL) != 0) { 1752f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank method = "CANCEL"; 1753c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } else { 1754c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank method = "REPLY"; 1755f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank isReply = true; 1756c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1757c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 17588d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank try { 17595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Create our iCalendar writer and start generating tags 17605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank SimpleIcsWriter ics = new SimpleIcsWriter(); 17618d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("BEGIN", "VCALENDAR"); 17628d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("METHOD", method); 17638d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("PRODID", "AndroidEmail"); 17648d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("VERSION", "2.0"); 1765820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 1766820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Our default vcalendar time zone is UTC, but this will change (below) if we're 1767820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // sending a recurring event, in which case we use local time 1768820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank TimeZone vCalendarTimeZone = sGmtTimeZone; 1769c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank String vCalendarDateSuffix = ""; 1770c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank 1771c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // Check for all day event 1772c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank boolean allDayEvent = false; 1773c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (entityValues.containsKey(Events.ALL_DAY)) { 1774c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank Integer ade = entityValues.getAsInteger(Events.ALL_DAY); 1775c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank allDayEvent = (ade != null) && (ade == 1); 1776c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (allDayEvent) { 1777c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // Example: DTSTART;VALUE=DATE:20100331 (all day event) 1778c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank vCalendarDateSuffix = ";VALUE=DATE"; 1779c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank } 1780c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank } 1781820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 1782820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If we're inviting people and the meeting is recurring, we need to send our time zone 1783c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // information and make sure to send DTSTART/DTEND in local time (unless, of course, 1784b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank // this is an all-day event). Recurring, for this purpose, includes exceptions to 1785b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank // recurring events 1786b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank if (!isReply && !allDayEvent && 1787b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank (entityValues.containsKey(Events.RRULE) || 17889e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik entityValues.containsKey(Events.ORIGINAL_SYNC_ID))) { 1789820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank vCalendarTimeZone = TimeZone.getDefault(); 1790820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write the VTIMEZONE block to the writer 1791820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank timeZoneToVTimezone(vCalendarTimeZone, ics); 1792c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // Example: DTSTART;TZID=US/Pacific:20100331T124500 1793c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank vCalendarDateSuffix = ";TZID=" + vCalendarTimeZone.getID(); 1794820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 1795820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 17968d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("BEGIN", "VEVENT"); 17978d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (uid == null) { 179804c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik uid = entityValues.getAsString(Events.SYNC_DATA2); 17998d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 18008d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (uid != null) { 18018d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("UID", uid); 18028d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1803c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 18045c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (entityValues.containsKey("DTSTAMP")) { 18055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("DTSTAMP", entityValues.getAsString("DTSTAMP")); 18065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } else { 180779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank ics.writeTag("DTSTAMP", millisToEasDateTime(System.currentTimeMillis())); 18088d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1809c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 18108d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank long startTime = entityValues.getAsLong(Events.DTSTART); 18115c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (startTime != 0) { 1812c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("DTSTART" + vCalendarDateSuffix, 1813c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank millisToEasDateTime(startTime, vCalendarTimeZone, !allDayEvent)); 18145c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 1815c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1816dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // If this is an Exception, we send the recurrence-id, which is just the original 1817dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // instance time 1818dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (isException) { 181904e4cdadbe23670fd99d21ffb88af40ef77d69aeJay Shrauner // isException indicates this key is present 1820dafc866120dac68fabbddcc9943e3995894c5244Marc Blank long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 1821c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("RECURRENCE-ID" + vCalendarDateSuffix, 1822c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank millisToEasDateTime(originalTime, vCalendarTimeZone, !allDayEvent)); 1823dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1824dafc866120dac68fabbddcc9943e3995894c5244Marc Blank 18258d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (!entityValues.containsKey(Events.DURATION)) { 18268d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (entityValues.containsKey(Events.DTEND)) { 1827c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("DTEND" + vCalendarDateSuffix, 182879268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank millisToEasDateTime( 1829c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank entityValues.getAsLong(Events.DTEND), vCalendarTimeZone, 1830c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank !allDayEvent)); 18318d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 18328d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } else { 18338d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Convert this into millis and add it to DTSTART for DTEND 18348d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // We'll use 1 hour as a default 18358d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank long durationMillis = HOURS; 18368d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Duration duration = new Duration(); 18378d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank try { 18388d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank duration.parse(entityValues.getAsString(Events.DURATION)); 183989bee1e3d03b439f4084bc9962bb3cbffd0b878aMarc Blank durationMillis = duration.getMillis(); 1840e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } catch (DateException e) { 18418d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // We'll use the default in this case 18428d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1843c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("DTEND" + vCalendarDateSuffix, 184479268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank millisToEasDateTime( 1845c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank startTime + durationMillis, vCalendarTimeZone, !allDayEvent)); 1846c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1847c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1848dafc866120dac68fabbddcc9943e3995894c5244Marc Blank String location = null; 18498d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (entityValues.containsKey(Events.EVENT_LOCATION)) { 1850dafc866120dac68fabbddcc9943e3995894c5244Marc Blank location = entityValues.getAsString(Events.EVENT_LOCATION); 1851dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ics.writeTag("LOCATION", location); 1852dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1853dafc866120dac68fabbddcc9943e3995894c5244Marc Blank 185404c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik String sequence = entityValues.getAsString(SYNC_VERSION); 1855dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (sequence == null) { 1856dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sequence = "0"; 18578d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 18585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 185946e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank // We'll use 0 to mean a meeting invitation 18605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank int titleId = 0; 18615c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank switch (messageFlag) { 18625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_INVITE: 186346e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank if (!sequence.equals("0")) { 1864dafc866120dac68fabbddcc9943e3995894c5244Marc Blank titleId = R.string.meeting_updated; 1865dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 18665c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 18675c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_ACCEPT: 18685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank titleId = R.string.meeting_accepted; 18695c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 18705c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_DECLINE: 18715c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank titleId = R.string.meeting_declined; 18725c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 18735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_TENTATIVE: 18745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank titleId = R.string.meeting_tentative; 18755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 187630d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank case Message.FLAG_OUTGOING_MEETING_CANCEL: 187730d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank titleId = R.string.meeting_canceled; 187830d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank break; 18795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 1880dafc866120dac68fabbddcc9943e3995894c5244Marc Blank Resources resources = context.getResources(); 18818d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String title = entityValues.getAsString(Events.TITLE); 18825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (title == null) { 18835c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank title = ""; 18848d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 18855c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("SUMMARY", title); 188646e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank // For meeting invitations just use the title 188746e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank if (titleId == 0) { 188846e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank msg.mSubject = title; 188946e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank } else { 189046e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank // Otherwise, use the additional text 1891dafc866120dac68fabbddcc9943e3995894c5244Marc Blank msg.mSubject = resources.getString(titleId, title); 189230d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank } 189300702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank 189400702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // Build the text for the message, starting with an initial line describing the 189500702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // exception (if this is one) 189600702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank StringBuilder sb = new StringBuilder(); 1897f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if (isException && !isReply) { 189800702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // Add the line, depending on whether this is a cancellation or update 189904e4cdadbe23670fd99d21ffb88af40ef77d69aeJay Shrauner // isException indicates this key is present 190000702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank Date date = new Date(entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME)); 190100702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank String dateString = DateFormat.getDateInstance().format(date); 190200702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank if (titleId == R.string.meeting_canceled) { 190300702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank sb.append(resources.getString(R.string.exception_cancel, dateString)); 190400702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } else { 190500702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank sb.append(resources.getString(R.string.exception_updated, dateString)); 190600702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } 190700702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank sb.append("\n\n"); 190800702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } 190900702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank String text = 191000702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank CalendarUtilities.buildMessageTextFromEntityValues(context, entityValues, sb); 191100702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank 191200702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank if (text.length() > 0) { 191300702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank ics.writeTag("DESCRIPTION", text); 191400702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } 191500702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // And store the message text 191600702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank msg.mText = text; 1917f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if (!isReply) { 19185c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (entityValues.containsKey(Events.ALL_DAY)) { 19195c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Integer ade = entityValues.getAsInteger(Events.ALL_DAY); 19205c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("X-MICROSOFT-CDO-ALLDAYEVENT", ade == 0 ? "FALSE" : "TRUE"); 19215c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 1922c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 19235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank String rrule = entityValues.getAsString(Events.RRULE); 19245c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (rrule != null) { 19255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("RRULE", rrule); 19265c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 19275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 192800702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // If we decide to send alarm information in the meeting request ics file, 192900702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // handle it here by looping through the subvalues 1930c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1931c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1932332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank // Handle attendee data here; determine "to" list and add ATTENDEE tags to ics 19338d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String organizerName = null; 19348d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String organizerEmail = null; 19358d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ArrayList<Address> toList = new ArrayList<Address>(); 19368d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank for (NamedContentValues ncv: subValues) { 19378d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Uri ncvUri = ncv.uri; 19388d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ContentValues ncvValues = ncv.values; 19398d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (ncvUri.equals(Attendees.CONTENT_URI)) { 194020a434b03d719d53b82e2628759210670f1e51a9Jay Shrauner final Integer relationship = 19418d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ncvValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP); 194220a434b03d719d53b82e2628759210670f1e51a9Jay Shrauner final String attendeeEmail = 194320a434b03d719d53b82e2628759210670f1e51a9Jay Shrauner ncvValues.getAsString(Attendees.ATTENDEE_EMAIL); 19448d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // If there's no relationship, we can't create this for EAS 19458d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Similarly, we need an attendee email for each invitee 194620a434b03d719d53b82e2628759210670f1e51a9Jay Shrauner if (relationship != null && !TextUtils.isEmpty(attendeeEmail)) { 19478d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Organizer isn't among attendees in EAS 19488d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (relationship == Attendees.RELATIONSHIP_ORGANIZER) { 19498d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank organizerName = ncvValues.getAsString(Attendees.ATTENDEE_NAME); 195020a434b03d719d53b82e2628759210670f1e51a9Jay Shrauner organizerEmail = attendeeEmail; 19518d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank continue; 1952c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 19538d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String attendeeName = ncvValues.getAsString(Attendees.ATTENDEE_NAME); 195424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 195524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank // If we only want to send to the specifiedAttendee, eliminate others here 195624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if ((specifiedAttendee != null) && 195724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank !attendeeEmail.equalsIgnoreCase(specifiedAttendee)) { 195824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank continue; 1959c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 196024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 196124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank addAttendeeToMessage(ics, toList, attendeeName, attendeeEmail, messageFlag, 196224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank account); 1963c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1964c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1965c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1966c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 196724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank // Manually add the specifiedAttendee if he wasn't added in the Attendees loop 196824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (toList.isEmpty() && (specifiedAttendee != null)) { 196924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank addAttendeeToMessage(ics, toList, null, specifiedAttendee, messageFlag, account); 197024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 197124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 19728d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Create the organizer tag for ical 19738d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (organizerEmail != null) { 19748d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String icalTag = "ORGANIZER"; 19758d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // We should be able to find this, assuming the Email is the user's email 19768d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // TODO Find this in the account 19778d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (organizerName != null) { 1978bc0c8c1523fc0bc42eb82dba6c2977492e441c03Makoto Onuki icalTag += ";CN=" + SimpleIcsWriter.quoteParamValue(organizerName); 19798d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 19808d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag(icalTag, "MAILTO:" + organizerEmail); 1981f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if (isReply) { 19828d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank toList.add(organizerName == null ? new Address(organizerEmail) : 19838d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank new Address(organizerEmail, organizerName)); 19848d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1985c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1986c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 198724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank // If we have no "to" list, we're done 198824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (toList.isEmpty()) return null; 1989601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank 19908d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Write out the "to" list 19918d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Address[] toArray = new Address[toList.size()]; 19928d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank int i = 0; 19938d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank for (Address address: toList) { 19948d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank toArray[i++] = address; 19958d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1996e89ec9216167fa7246ae268f2b2062e0a93621bfJames Lemieux msg.mTo = Address.toHeader(toArray); 19978d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank 1998820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank ics.writeTag("CLASS", "PUBLIC"); 1999dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ics.writeTag("STATUS", (messageFlag == Message.FLAG_OUTGOING_MEETING_CANCEL) ? 2000dafc866120dac68fabbddcc9943e3995894c5244Marc Blank "CANCELLED" : "CONFIRMED"); 2001820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank ics.writeTag("TRANSP", "OPAQUE"); // What Exchange uses 2002820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank ics.writeTag("PRIORITY", "5"); // 1 to 9, 5 = medium 2003dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ics.writeTag("SEQUENCE", sequence); 20048d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("END", "VEVENT"); 20058d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("END", "VCALENDAR"); 20065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 20075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Create the ics attachment using the "content" field 20085c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Attachment att = new Attachment(); 2009bc0c8c1523fc0bc42eb82dba6c2977492e441c03Makoto Onuki att.mContentBytes = ics.getBytes(); 20105c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank att.mMimeType = "text/calendar; method=" + method; 20115c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank att.mFileName = "invite.ics"; 2012e54b75dc4f638e594e9b97e3b4ed8829fbc9b521Makoto Onuki att.mSize = att.mContentBytes.length; 20135c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // We don't send content-disposition with this attachment 2014b4d217e5170ae397d741e95308d98e80d0c2f637Marc Blank att.mFlags = Attachment.FLAG_ICS_ALTERNATIVE_PART; 20155c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 20165c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Add the attachment to the message 20175c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank msg.mAttachments = new ArrayList<Attachment>(); 20185c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank msg.mAttachments.add(att); 20195c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } catch (IOException e) { 2020942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy LogUtils.w(TAG, "IOException in createMessageForEntity"); 20218d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank return null; 2022c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2023c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 2024c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank // Return the new Message to caller 2025c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank return msg; 2026c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2027c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 2028c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank /** 20296989716b639d274a98141674556ac9402be32ebeRoboErik * Create a Message for an Event that can be retrieved from CalendarProvider 20306989716b639d274a98141674556ac9402be32ebeRoboErik * by its unique id 20316989716b639d274a98141674556ac9402be32ebeRoboErik * 2032c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param cr a content resolver that can be used to query for the Event 2033c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param eventId the unique id of the Event 20346989716b639d274a98141674556ac9402be32ebeRoboErik * @param messageFlag the Message.FLAG_XXX constant indicating the type of 20356989716b639d274a98141674556ac9402be32ebeRoboErik * email to be sent 20366989716b639d274a98141674556ac9402be32ebeRoboErik * @param the unique id of this Event, or null if it can be retrieved from 20376989716b639d274a98141674556ac9402be32ebeRoboErik * the Event 2038c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param the user's account 20396989716b639d274a98141674556ac9402be32ebeRoboErik * @param requireAddressees if true (the default), no Message is returned if 20406989716b639d274a98141674556ac9402be32ebeRoboErik * there aren't any addressees; if false, return the Message 20416989716b639d274a98141674556ac9402be32ebeRoboErik * regardless (addressees will be filled in later) 2042c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @return a Message with many fields pre-filled (more later) 2043c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank */ 20445c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank static public EmailContent.Message createMessageForEventId(Context context, long eventId, 2045b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Hu int messageFlag, String uid, Account account) { 2046601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank return createMessageForEventId(context, eventId, messageFlag, uid, account, 20476989716b639d274a98141674556ac9402be32ebeRoboErik null /* specifiedAttendee */); 2048601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank } 2049601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank 2050601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank static public EmailContent.Message createMessageForEventId(Context context, long eventId, 2051b31070f7484eeeb15c4bce89dbc61388d05d0bfcYu Ping Hu int messageFlag, String uid, Account account, String specifiedAttendee) { 2052185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner final ContentResolver cr = context.getContentResolver(); 2053185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner final Cursor cursor = cr.query(ContentUris.withAppendedId( 2054185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner Events.CONTENT_URI, eventId), null, null, null, null); 2055185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner if (cursor == null) { 2056185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner return null; 2057185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner } 2058185060cbeb39dc4539fbc0c72a865d8ec8d12979Jay Shrauner final EntityIterator eventIterator = EventsEntity.newEntityIterator(cursor, cr); 2059c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank try { 2060c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank while (eventIterator.hasNext()) { 2061c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank Entity entity = eventIterator.next(); 2062601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank return createMessageForEntity(context, entity, messageFlag, uid, account, 206324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank specifiedAttendee); 2064c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2065c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } finally { 2066c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank eventIterator.close(); 2067c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2068c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank return null; 2069c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2070393208ab154d18a876842777781ab153d34a0281Marc Blank 2071393208ab154d18a876842777781ab153d34a0281Marc Blank /** 2072393208ab154d18a876842777781ab153d34a0281Marc Blank * Return a boolean value for an integer ContentValues column 2073393208ab154d18a876842777781ab153d34a0281Marc Blank * @param values a ContentValues object 2074393208ab154d18a876842777781ab153d34a0281Marc Blank * @param columnName the name of a column to be found in the ContentValues 2075393208ab154d18a876842777781ab153d34a0281Marc Blank * @return a boolean representation of the value of columnName in values; null and 0 = false, 2076393208ab154d18a876842777781ab153d34a0281Marc Blank * other integers = true 2077393208ab154d18a876842777781ab153d34a0281Marc Blank */ 2078393208ab154d18a876842777781ab153d34a0281Marc Blank static public boolean getIntegerValueAsBoolean(ContentValues values, String columnName) { 2079393208ab154d18a876842777781ab153d34a0281Marc Blank Integer intValue = values.getAsInteger(columnName); 2080393208ab154d18a876842777781ab153d34a0281Marc Blank return (intValue != null && intValue != 0); 2081393208ab154d18a876842777781ab153d34a0281Marc Blank } 20828e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda} 2083