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; 2777110d3a646dd691d84abd0b1e083385c1418ac5Marc Blankimport android.net.Uri; 28c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport android.os.RemoteException; 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; 36f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blankimport android.util.Log; 375862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3860306788d571f234883d9833a97216c40289d0cfMichael Chanimport com.android.calendarcommon2.DateException; 3960306788d571f234883d9833a97216c40289d0cfMichael Chanimport com.android.calendarcommon2.Duration; 40f352bc9f29cacc61b195069e48d5c8b868660694Marc Blankimport com.android.emailcommon.Logging; 4160df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.mail.Address; 4260df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.Account; 4360df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.EmailContent; 4460df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 4560df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 4660df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.provider.Mailbox; 4760df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.service.AccountServiceProxy; 4860df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.emailcommon.utility.Utility; 4960df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.Eas; 5060df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.EasSyncService; 5160df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.ExchangeService; 5260df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.R; 5360df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.adapter.Serializer; 5460df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.android.exchange.adapter.Tags; 55b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrookimport com.android.internal.util.ArrayUtils; 5660df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blankimport com.google.common.annotations.VisibleForTesting; 5760df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank 5814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blankimport java.io.IOException; 59dafc866120dac68fabbddcc9943e3995894c5244Marc Blankimport java.text.DateFormat; 60c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blankimport java.util.ArrayList; 615862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.Calendar; 625862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.Date; 635862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.GregorianCalendar; 645862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.HashMap; 655862a85e17e81866ca82a9905577931947fbd44eMarc Blankimport java.util.TimeZone; 665862a85e17e81866ca82a9905577931947fbd44eMarc Blank 675862a85e17e81866ca82a9905577931947fbd44eMarc Blankpublic class CalendarUtilities { 6804c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik 695862a85e17e81866ca82a9905577931947fbd44eMarc Blank // NOTE: Most definitions in this class are have package visibility for testing purposes 705862a85e17e81866ca82a9905577931947fbd44eMarc Blank private static final String TAG = "CalendarUtility"; 715862a85e17e81866ca82a9905577931947fbd44eMarc Blank 725862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Time related convenience constants, in milliseconds 735862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int SECONDS = 1000; 745862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MINUTES = SECONDS*60; 755862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int HOURS = MINUTES*60; 76377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static final long DAYS = HOURS*24; 775862a85e17e81866ca82a9905577931947fbd44eMarc Blank 782c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // We want to find a time zone whose DST info is accurate to one minute 792c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static final int STANDARD_DST_PRECISION = MINUTES; 802c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // If we can't find one, we'll try a more lenient standard (this is better than guessing a 812c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // time zone, which is what we otherwise do). Note that this specifically addresses an issue 822c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // seen in some time zones sent by MS Exchange in which the start and end hour differ 832c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank // for no apparent reason 842c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static final int LENIENT_DST_PRECISION = 4*HOURS; 852c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank 8604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik private static final String SYNC_VERSION = Events.SYNC_DATA4; 875862a85e17e81866ca82a9905577931947fbd44eMarc Blank // NOTE All Microsoft data structures are little endian 885862a85e17e81866ca82a9905577931947fbd44eMarc Blank 895862a85e17e81866ca82a9905577931947fbd44eMarc Blank // The following constants relate to standard Microsoft data sizes 905862a85e17e81866ca82a9905577931947fbd44eMarc Blank // For documentation, see http://msdn.microsoft.com/en-us/library/aa505945.aspx 915862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_LONG_SIZE = 4; 925862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_WCHAR_SIZE = 2; 935862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_WORD_SIZE = 2; 945862a85e17e81866ca82a9905577931947fbd44eMarc Blank 955862a85e17e81866ca82a9905577931947fbd44eMarc Blank // The following constants relate to Microsoft's SYSTEMTIME structure 965862a85e17e81866ca82a9905577931947fbd44eMarc Blank // For documentation, see: http://msdn.microsoft.com/en-us/library/ms724950(VS.85).aspx?ppud=4 975862a85e17e81866ca82a9905577931947fbd44eMarc Blank 985862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_YEAR = 0 * MSFT_WORD_SIZE; 995862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_MONTH = 1 * MSFT_WORD_SIZE; 1005862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_DAY_OF_WEEK = 2 * MSFT_WORD_SIZE; 1015862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_DAY = 3 * MSFT_WORD_SIZE; 1025862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_HOUR = 4 * MSFT_WORD_SIZE; 1035862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_MINUTE = 5 * MSFT_WORD_SIZE; 1045862a85e17e81866ca82a9905577931947fbd44eMarc Blank //static final int MSFT_SYSTEMTIME_SECONDS = 6 * MSFT_WORD_SIZE; 1055862a85e17e81866ca82a9905577931947fbd44eMarc Blank //static final int MSFT_SYSTEMTIME_MILLIS = 7 * MSFT_WORD_SIZE; 1065862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_SYSTEMTIME_SIZE = 8*MSFT_WORD_SIZE; 1075862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1085862a85e17e81866ca82a9905577931947fbd44eMarc Blank // The following constants relate to Microsoft's TIME_ZONE_INFORMATION structure 1095862a85e17e81866ca82a9905577931947fbd44eMarc Blank // For documentation, see http://msdn.microsoft.com/en-us/library/ms725481(VS.85).aspx 1107b873db28bda10f98f3782b7b161cb66117034d7Marc Blank static final int MSFT_TIME_ZONE_STRING_SIZE = 32; 1117b873db28bda10f98f3782b7b161cb66117034d7Marc Blank 1125862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_BIAS_OFFSET = 0; 1135862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_STANDARD_NAME_OFFSET = 1145862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_BIAS_OFFSET + MSFT_LONG_SIZE; 1155862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_STANDARD_DATE_OFFSET = 1167b873db28bda10f98f3782b7b161cb66117034d7Marc Blank MSFT_TIME_ZONE_STANDARD_NAME_OFFSET + (MSFT_WCHAR_SIZE*MSFT_TIME_ZONE_STRING_SIZE); 1175862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_STANDARD_BIAS_OFFSET = 1185862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_STANDARD_DATE_OFFSET + MSFT_SYSTEMTIME_SIZE; 1195862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET = 1205862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_STANDARD_BIAS_OFFSET + MSFT_LONG_SIZE; 1215862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET = 1227b873db28bda10f98f3782b7b161cb66117034d7Marc Blank MSFT_TIME_ZONE_DAYLIGHT_NAME_OFFSET + (MSFT_WCHAR_SIZE*MSFT_TIME_ZONE_STRING_SIZE); 1235862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET = 1245862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET + MSFT_SYSTEMTIME_SIZE; 1255862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final int MSFT_TIME_ZONE_SIZE = 1265862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET + MSFT_LONG_SIZE; 1275862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1285862a85e17e81866ca82a9905577931947fbd44eMarc Blank // TimeZone cache; we parse/decode as little as possible, because the process is quite slow 1295862a85e17e81866ca82a9905577931947fbd44eMarc Blank private static HashMap<String, TimeZone> sTimeZoneCache = new HashMap<String, TimeZone>(); 130377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // TZI string cache; we keep around our encoded TimeZoneInformation strings 131377230593dca7cb01483bfaf93959e5821f5f028Marc Blank private static HashMap<TimeZone, String> sTziStringCache = new HashMap<TimeZone, String>(); 1325862a85e17e81866ca82a9905577931947fbd44eMarc Blank 133270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC"); 134937af5abcbc1268f22a3058b00835c74ba20f116RoboErik // Default, Popup 13580a8e57ce2dc2695ed6f35599d326090e4ad9faeRoboErik private static final String ALLOWED_REMINDER_TYPES = "0,1"; 136937af5abcbc1268f22a3058b00835c74ba20f116RoboErik // None, required, optional 137937af5abcbc1268f22a3058b00835c74ba20f116RoboErik private static final String ALLOWED_ATTENDEE_TYPES = "0,1,2"; 138937af5abcbc1268f22a3058b00835c74ba20f116RoboErik // Busy, free, tentative 139937af5abcbc1268f22a3058b00835c74ba20f116RoboErik private static final String ALLOWED_AVAILABILITIES = "0,1,2"; 140270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank 1415862a85e17e81866ca82a9905577931947fbd44eMarc Blank // There is no type 4 (thus, the "") 1425862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final String[] sTypeToFreq = 1435862a85e17e81866ca82a9905577931947fbd44eMarc Blank new String[] {"DAILY", "WEEKLY", "MONTHLY", "MONTHLY", "", "YEARLY", "YEARLY"}; 1445862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1455862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final String[] sDayTokens = 1465862a85e17e81866ca82a9905577931947fbd44eMarc Blank new String[] {"SU", "MO", "TU", "WE", "TH", "FR", "SA"}; 1475862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1485862a85e17e81866ca82a9905577931947fbd44eMarc Blank static final String[] sTwoCharacterNumbers = 1495862a85e17e81866ca82a9905577931947fbd44eMarc Blank new String[] {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}; 1505862a85e17e81866ca82a9905577931947fbd44eMarc Blank 151f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Bits used in EAS recurrences for days of the week 152f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_SUNDAY = 1<<0; 153f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_MONDAY = 1<<1; 154f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_TUESDAY = 1<<2; 155f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_WEDNESDAY = 1<<3; 156f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_THURSDAY = 1<<4; 157f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_FRIDAY = 1<<5; 158f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_SATURDAY = 1<<6; 159f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_WEEKDAYS = 160f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank EAS_MONDAY | EAS_TUESDAY | EAS_WEDNESDAY | EAS_THURSDAY | EAS_FRIDAY; 161f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank protected static final int EAS_WEEKENDS = EAS_SATURDAY | EAS_SUNDAY; 162f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank 163377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static final int sCurrentYear = new GregorianCalendar().get(Calendar.YEAR); 164377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static final TimeZone sGmtTimeZone = TimeZone.getTimeZone("GMT"); 165377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 166bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank private static final String ICALENDAR_ATTENDEE = "ATTENDEE;ROLE=REQ-PARTICIPANT"; 167bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_CANCEL = ICALENDAR_ATTENDEE; 168bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_INVITE = 169bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=NEEDS-ACTION;RSVP=TRUE"; 170bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_ACCEPT = 171bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=ACCEPTED"; 172bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_DECLINE = 173bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=DECLINED"; 174bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank static final String ICALENDAR_ATTENDEE_TENTATIVE = 175bf1de871b7ec63c93694ba022282e8789e69f201Marc Blank ICALENDAR_ATTENDEE + ";PARTSTAT=TENTATIVE"; 176c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 177e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // Note that these constants apply to Calendar items 178e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // For future reference: MeetingRequest data can also include free/busy information, but the 179e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // constants for these four options in MeetingRequest data have different values! 180e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // See [MS-ASCAL] 2.2.2.8 for Calendar BusyStatus 181e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank // See [MS-EMAIL] 2.2.2.34 for MeetingRequest BusyStatus 182e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_FREE = 0; 183e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_TENTATIVE = 1; 184e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_BUSY = 2; 185e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank public static final int BUSY_STATUS_OUT_OF_OFFICE = 3; 186dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank 18765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank // Note that these constants apply to Calendar items, and are used in EAS 14+ 18865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank // See [MS-ASCAL] 2.2.2.22 for Calendar ResponseType 18965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_NONE = 0; 19065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_ORGANIZER = 1; 19165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_TENTATIVE = 2; 19265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_ACCEPTED = 3; 19365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_DECLINED = 4; 19465d022dc43e4461e86fd7bc143591f542b07428bMarc Blank public static final int RESPONSE_TYPE_NOT_RESPONDED = 5; 19565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank 1965862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Return a 4-byte long from a byte array (little endian) 1975862a85e17e81866ca82a9905577931947fbd44eMarc Blank static int getLong(byte[] bytes, int offset) { 1985862a85e17e81866ca82a9905577931947fbd44eMarc Blank return (bytes[offset++] & 0xFF) | ((bytes[offset++] & 0xFF) << 8) | 1995862a85e17e81866ca82a9905577931947fbd44eMarc Blank ((bytes[offset++] & 0xFF) << 16) | ((bytes[offset] & 0xFF) << 24); 2005862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2015862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2025862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Put a 4-byte long into a byte array (little endian) 2035862a85e17e81866ca82a9905577931947fbd44eMarc Blank static void setLong(byte[] bytes, int offset, int value) { 2045862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) (value & 0xFF); 2055862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) ((value >> 8) & 0xFF); 2065862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) ((value >> 16) & 0xFF); 2075862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset] = (byte) ((value >> 24) & 0xFF); 2085862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2095862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2105862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Return a 2-byte word from a byte array (little endian) 2115862a85e17e81866ca82a9905577931947fbd44eMarc Blank static int getWord(byte[] bytes, int offset) { 2125862a85e17e81866ca82a9905577931947fbd44eMarc Blank return (bytes[offset++] & 0xFF) | ((bytes[offset] & 0xFF) << 8); 2135862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2145862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2155862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Put a 2-byte word into a byte array (little endian) 2165862a85e17e81866ca82a9905577931947fbd44eMarc Blank static void setWord(byte[] bytes, int offset, int value) { 2175862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset++] = (byte) (value & 0xFF); 2185862a85e17e81866ca82a9905577931947fbd44eMarc Blank bytes[offset] = (byte) ((value >> 8) & 0xFF); 2195862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2205862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2217b873db28bda10f98f3782b7b161cb66117034d7Marc Blank static String getString(byte[] bytes, int offset, int size) { 2227b873db28bda10f98f3782b7b161cb66117034d7Marc Blank StringBuilder sb = new StringBuilder(); 2237b873db28bda10f98f3782b7b161cb66117034d7Marc Blank for (int i = 0; i < size; i++) { 2247b873db28bda10f98f3782b7b161cb66117034d7Marc Blank int ch = bytes[offset + i]; 2257b873db28bda10f98f3782b7b161cb66117034d7Marc Blank if (ch == 0) { 2267b873db28bda10f98f3782b7b161cb66117034d7Marc Blank break; 2277b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } else { 2287b873db28bda10f98f3782b7b161cb66117034d7Marc Blank sb.append((char)ch); 2297b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } 2307b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } 2317b873db28bda10f98f3782b7b161cb66117034d7Marc Blank return sb.toString(); 2327b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } 2337b873db28bda10f98f3782b7b161cb66117034d7Marc Blank 2345862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Internal structure for storing a time zone date from a SYSTEMTIME structure 2355862a85e17e81866ca82a9905577931947fbd44eMarc Blank // This date represents either the start or the end time for DST 2365862a85e17e81866ca82a9905577931947fbd44eMarc Blank static class TimeZoneDate { 2375862a85e17e81866ca82a9905577931947fbd44eMarc Blank String year; 2385862a85e17e81866ca82a9905577931947fbd44eMarc Blank int month; 2395862a85e17e81866ca82a9905577931947fbd44eMarc Blank int dayOfWeek; 2405862a85e17e81866ca82a9905577931947fbd44eMarc Blank int day; 2415862a85e17e81866ca82a9905577931947fbd44eMarc Blank int time; 2425862a85e17e81866ca82a9905577931947fbd44eMarc Blank int hour; 2435862a85e17e81866ca82a9905577931947fbd44eMarc Blank int minute; 2445862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 2455862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2464868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank @VisibleForTesting 2474868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank static void clearTimeZoneCache() { 2484868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank sTimeZoneCache.clear(); 2494868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank } 2504868e0f09b58104741a5593f6097589ac62c6ce3Marc Blank 251820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static void putRuleIntoTimeZoneInformation(byte[] bytes, int offset, RRule rrule, int hour, 252820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int minute) { 253820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // MSFT months are 1 based, same as RRule 254820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MONTH, rrule.month); 255820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // MSFT day of week starts w/ Sunday = 0; RRule starts w/ Sunday = 1 256820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY_OF_WEEK, rrule.dayOfWeek - 1); 257820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // 5 means "last" in MSFT land; for RRule, it's -1 258820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY, rrule.week < 0 ? 5 : rrule.week); 259820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Turn hours/minutes into ms from midnight (per TimeZone) 260820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_HOUR, hour); 261820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MINUTE, minute); 262820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 263820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 26410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank // Write a transition time into SYSTEMTIME data (via an offset into a byte array) 26510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank static void putTransitionMillisIntoSystemTime(byte[] bytes, int offset, long millis) { 266377230593dca7cb01483bfaf93959e5821f5f028Marc Blank GregorianCalendar cal = new GregorianCalendar(TimeZone.getDefault()); 267377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // Round to the next highest minute; we always write seconds as zero 268377230593dca7cb01483bfaf93959e5821f5f028Marc Blank cal.setTimeInMillis(millis + 30*SECONDS); 269377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 270377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // MSFT months are 1 based; TimeZone is 0 based 271377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MONTH, cal.get(Calendar.MONTH) + 1); 272377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // MSFT day of week starts w/ Sunday = 0; TimeZone starts w/ Sunday = 1 273377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY_OF_WEEK, cal.get(Calendar.DAY_OF_WEEK) - 1); 274377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 275377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // Get the "day" in TimeZone format 276377230593dca7cb01483bfaf93959e5821f5f028Marc Blank int wom = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); 277377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // 5 means "last" in MSFT land; for TimeZone, it's -1 278377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_DAY, wom < 0 ? 5 : wom); 279377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 280377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // Turn hours/minutes into ms from midnight (per TimeZone) 28110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_HOUR, getTrueTransitionHour(cal)); 28210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank setWord(bytes, offset + MSFT_SYSTEMTIME_MINUTE, getTrueTransitionMinute(cal)); 283377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 284377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 2855862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Build a TimeZoneDate structure from a SYSTEMTIME within a byte array at a given offset 2865862a85e17e81866ca82a9905577931947fbd44eMarc Blank static TimeZoneDate getTimeZoneDateFromSystemTime(byte[] bytes, int offset) { 2875862a85e17e81866ca82a9905577931947fbd44eMarc Blank TimeZoneDate tzd = new TimeZoneDate(); 2885862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2895862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT year is an int; TimeZone is a String 2905862a85e17e81866ca82a9905577931947fbd44eMarc Blank int num = getWord(bytes, offset + MSFT_SYSTEMTIME_YEAR); 2915862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.year = Integer.toString(num); 2925862a85e17e81866ca82a9905577931947fbd44eMarc Blank 2935862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT month = 0 means no daylight time 2945862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT months are 1 based; TimeZone is 0 based 2955862a85e17e81866ca82a9905577931947fbd44eMarc Blank num = getWord(bytes, offset + MSFT_SYSTEMTIME_MONTH); 2965862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (num == 0) { 2975862a85e17e81866ca82a9905577931947fbd44eMarc Blank return null; 2985862a85e17e81866ca82a9905577931947fbd44eMarc Blank } else { 2995862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.month = num -1; 3005862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3015862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3025862a85e17e81866ca82a9905577931947fbd44eMarc Blank // MSFT day of week starts w/ Sunday = 0; TimeZone starts w/ Sunday = 1 3035862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.dayOfWeek = getWord(bytes, offset + MSFT_SYSTEMTIME_DAY_OF_WEEK) + 1; 3045862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3055862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Get the "day" in TimeZone format 3065862a85e17e81866ca82a9905577931947fbd44eMarc Blank num = getWord(bytes, offset + MSFT_SYSTEMTIME_DAY); 3075862a85e17e81866ca82a9905577931947fbd44eMarc Blank // 5 means "last" in MSFT land; for TimeZone, it's -1 3085862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (num == 5) { 3095862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.day = -1; 3105862a85e17e81866ca82a9905577931947fbd44eMarc Blank } else { 3115862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.day = num; 3125862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3135862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3145862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Turn hours/minutes into ms from midnight (per TimeZone) 3155862a85e17e81866ca82a9905577931947fbd44eMarc Blank int hour = getWord(bytes, offset + MSFT_SYSTEMTIME_HOUR); 3165862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.hour = hour; 3175862a85e17e81866ca82a9905577931947fbd44eMarc Blank int minute = getWord(bytes, offset + MSFT_SYSTEMTIME_MINUTE); 3185862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.minute = minute; 3195862a85e17e81866ca82a9905577931947fbd44eMarc Blank tzd.time = (hour*HOURS) + (minute*MINUTES); 3205862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3215862a85e17e81866ca82a9905577931947fbd44eMarc Blank return tzd; 3225862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3235862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3245862a85e17e81866ca82a9905577931947fbd44eMarc Blank /** 3255862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Build a GregorianCalendar, based on a time zone and TimeZoneDate. 3265862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @param timeZone the time zone we're checking 3275862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @param tzd the TimeZoneDate we're interested in 3285862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @return a GregorianCalendar with the given time zone and date 3295862a85e17e81866ca82a9905577931947fbd44eMarc Blank */ 33079268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static long getMillisAtTimeZoneDateTransition(TimeZone timeZone, TimeZoneDate tzd) { 3315862a85e17e81866ca82a9905577931947fbd44eMarc Blank GregorianCalendar testCalendar = new GregorianCalendar(timeZone); 332377230593dca7cb01483bfaf93959e5821f5f028Marc Blank testCalendar.set(GregorianCalendar.YEAR, sCurrentYear); 3335862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.MONTH, tzd.month); 3345862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.DAY_OF_WEEK, tzd.dayOfWeek); 3355862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.DAY_OF_WEEK_IN_MONTH, tzd.day); 3365862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.HOUR_OF_DAY, tzd.hour); 3375862a85e17e81866ca82a9905577931947fbd44eMarc Blank testCalendar.set(GregorianCalendar.MINUTE, tzd.minute); 33879268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank testCalendar.set(GregorianCalendar.SECOND, 0); 33979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank return testCalendar.getTimeInMillis(); 3405862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 3415862a85e17e81866ca82a9905577931947fbd44eMarc Blank 3425862a85e17e81866ca82a9905577931947fbd44eMarc Blank /** 343820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Return a GregorianCalendar representing the first standard/daylight transition between a 344820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * start time and an end time in the given time zone 345820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz a TimeZone the time zone in which we're looking for transitions 346377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param startTime the start time for the test 347377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param endTime the end time for the test 348377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param startInDaylightTime whether daylight time is in effect at the startTime 349820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return a GregorianCalendar representing the transition or null if none 350377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 35179268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static GregorianCalendar findTransitionDate(TimeZone tz, long startTime, 35210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank long endTime, boolean startInDaylightTime) { 353377230593dca7cb01483bfaf93959e5821f5f028Marc Blank long startingEndTime = endTime; 354377230593dca7cb01483bfaf93959e5821f5f028Marc Blank Date date = null; 35510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank 356820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll keep splitting the difference until we're within a minute 357377230593dca7cb01483bfaf93959e5821f5f028Marc Blank while ((endTime - startTime) > MINUTES) { 358377230593dca7cb01483bfaf93959e5821f5f028Marc Blank long checkTime = ((startTime + endTime) / 2) + 1; 359377230593dca7cb01483bfaf93959e5821f5f028Marc Blank date = new Date(checkTime); 36010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank boolean inDaylightTime = tz.inDaylightTime(date); 36110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank if (inDaylightTime != startInDaylightTime) { 362377230593dca7cb01483bfaf93959e5821f5f028Marc Blank endTime = checkTime; 363377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } else { 364377230593dca7cb01483bfaf93959e5821f5f028Marc Blank startTime = checkTime; 365377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 366377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 367820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 368820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If these are the same, we're really messed up; return null 369377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (endTime == startingEndTime) { 370820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 371377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 372820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 37310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank // Set up our calendar and return it 374820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar calendar = new GregorianCalendar(tz); 37510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank calendar.setTimeInMillis(startTime); 376820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return calendar; 377377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 378377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 379377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 380377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Return a Base64 representation of a MSFT TIME_ZONE_INFORMATION structure from a TimeZone 381377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * that might be found in an Event; use cached result, if possible 382377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param tz the TimeZone 383377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return the Base64 String representing a Microsoft TIME_ZONE_INFORMATION element 384377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 385377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static public String timeZoneToTziString(TimeZone tz) { 386377230593dca7cb01483bfaf93959e5821f5f028Marc Blank String tziString = sTziStringCache.get(tz); 387377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (tziString != null) { 388377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (Eas.USER_LOG) { 389385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.log(TAG, "TZI string for " + tz.getDisplayName() + 390385a0be662509754e687bcfa9813208b050bf951Marc Blank " found in cache."); 391377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 392377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return tziString; 393377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 394377230593dca7cb01483bfaf93959e5821f5f028Marc Blank tziString = timeZoneToTziStringImpl(tz); 395377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sTziStringCache.put(tz, tziString); 396377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return tziString; 397377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 398377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 399377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 400820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * A class for storing RRULE information. The RRULE members can be accessed individually or 401820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * an RRULE string can be created with toString() 402820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 403820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static class RRule { 404820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static final int RRULE_NONE = 0; 405820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static final int RRULE_DAY_WEEK = 1; 406820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static final int RRULE_DATE = 2; 407820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 408820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int type; 409820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int dayOfWeek; 410820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int week; 411820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int month; 412820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int date; 413820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 414820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 415820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Create an RRULE based on month and date 416820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _month the month (1 = JAN, 12 = DEC) 417820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _date the date in the month (1-31) 418820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 419820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule(int _month, int _date) { 420820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank type = RRULE_DATE; 421820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank month = _month; 422820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank date = _date; 423820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 424820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 425820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 426820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Create an RRULE based on month, day of week, and week # 427820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _month the month (1 = JAN, 12 = DEC) 428820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _dayOfWeek the day of the week (1 = SU, 7 = SA) 429820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param _week the week in the month (1-5 or -1 for last) 430820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 431820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule(int _month, int _dayOfWeek, int _week) { 432820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank type = RRULE_DAY_WEEK; 433820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank month = _month; 434820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank dayOfWeek = _dayOfWeek; 435820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank week = _week; 436820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 437820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 438820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank @Override 439820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank public String toString() { 440820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (type == RRULE_DAY_WEEK) { 441820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return "FREQ=YEARLY;BYMONTH=" + month + ";BYDAY=" + week + 442820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sDayTokens[dayOfWeek - 1]; 443820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 444820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return "FREQ=YEARLY;BYMONTH=" + month + ";BYMONTHDAY=" + date; 445820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 446820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 447820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 448820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 449820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 450820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Generate an RRULE string for an array of GregorianCalendars, if possible. For now, we are 451820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * only looking for rules based on the same date in a month or a specific instance of a day of 452820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the week in a month (e.g. 2nd Tuesday or last Friday). Indeed, these are the only kinds of 453820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * rules used in the current tzinfo database. 454820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param calendars an array of GregorianCalendar, set to a series of transition times in 455820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * consecutive years starting with the current year 456820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return an RRULE or null if none could be inferred from the calendars 457820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 45879268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static RRule inferRRuleFromCalendars(GregorianCalendar[] calendars) { 459820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Let's see if we can make a rule about these 460820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar calendar = calendars[0]; 461820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (calendar == null) return null; 462820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int month = calendar.get(Calendar.MONTH); 463820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int date = calendar.get(Calendar.DAY_OF_MONTH); 464820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); 465820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int week = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH); 466820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int maxWeek = calendar.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); 467820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean dateRule = false; 468820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean dayOfWeekRule = false; 469820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 1; i < calendars.length; i++) { 470820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar cal = calendars[i]; 471820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal == null) return null; 472820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If it's not the same month, there's no rule 473820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal.get(Calendar.MONTH) != month) { 474820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 475820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (dayOfWeek == cal.get(Calendar.DAY_OF_WEEK)) { 476820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Ok, it seems to be the same day of the week 477820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dateRule) { 478820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 479820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 480820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank dayOfWeekRule = true; 481820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int thisWeek = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); 482820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (week != thisWeek) { 483820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (week < 0 || week == maxWeek) { 484820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int thisMaxWeek = cal.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); 485820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (thisWeek == thisMaxWeek) { 486820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use -1 (i.e. last) week 487820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank week = -1; 488820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank continue; 489820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 490820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 491820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 492820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 493820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (date == cal.get(Calendar.DAY_OF_MONTH)) { 494820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Maybe the same day of the month? 495820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dayOfWeekRule) { 496820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 497820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 498820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank dateRule = true; 499820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 500820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return null; 501820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 502820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 503820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 504820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dateRule) { 505820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return new RRule(month + 1, date); 506820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 507820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // sDayTokens is 0 based (SU = 0); Calendar days of week are 1 based (SU = 1) 508820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // iCalendar months are 1 based; Calendar months are 0 based 509820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // So we adjust these when building the string 510820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return new RRule(month + 1, dayOfWeek, week); 511820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 512820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 513820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 514820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Generate an rfc2445 utcOffset from minutes offset from GMT 515820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * These look like +0800 or -0100 516820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param offsetMinutes minutes offset from GMT (east is positive, west is negative 517820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return a utcOffset 518820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 51979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static String utcOffsetString(int offsetMinutes) { 520820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank StringBuilder sb = new StringBuilder(); 521820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int hours = offsetMinutes / 60; 522820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hours < 0) { 523820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('-'); 524820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank hours = 0 - hours; 525820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 526820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('+'); 527820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 528820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int minutes = offsetMinutes % 60; 529820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hours < 10) { 530820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('0'); 531820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 532820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(hours); 533820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (minutes < 10) { 534820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('0'); 535820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 536820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(minutes); 537820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return sb.toString(); 538820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 539820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 540820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 541820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Fill the passed in GregorianCalendars arrays with DST transition information for this and 542820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the following years (based on the length of the arrays) 543820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz the time zone 544820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param toDaylightCalendars an array of GregorianCalendars, one for each year, representing 545820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the transition to daylight time 546820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param toStandardCalendars an array of GregorianCalendars, one for each year, representing 547820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * the transition to standard time 548820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return true if transitions could be found for all years, false otherwise 549820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 550820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static boolean getDSTCalendars(TimeZone tz, GregorianCalendar[] toDaylightCalendars, 551820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toStandardCalendars) { 552820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use the length of the arrays to determine how many years to check 553820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int maxYears = toDaylightCalendars.length; 554820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (toStandardCalendars.length != maxYears) { 555820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return false; 556820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 557820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Get the transitions for this year and the next few years 558820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 0; i < maxYears; i++) { 559820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar cal = new GregorianCalendar(tz); 560820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal.set(sCurrentYear + i, Calendar.JANUARY, 1, 0, 0, 0); 561820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long startTime = cal.getTimeInMillis(); 562820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Calculate end of year; no need to be insanely precise 563820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long endOfYearTime = startTime + (365*DAYS) + (DAYS>>2); 564820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank Date date = new Date(startTime); 565820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean startInDaylightTime = tz.inDaylightTime(date); 566820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Find the first transition, and store 567820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal = findTransitionDate(tz, startTime, endOfYearTime, startInDaylightTime); 568820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal == null) { 569820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return false; 570820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (startInDaylightTime) { 571820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toStandardCalendars[i] = cal; 572820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 573820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toDaylightCalendars[i] = cal; 574820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 575820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Find the second transition, and store 576820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal = findTransitionDate(tz, startTime, endOfYearTime, !startInDaylightTime); 577820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (cal == null) { 578820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return false; 579820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else if (startInDaylightTime) { 580820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toDaylightCalendars[i] = cal; 581820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 582820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toStandardCalendars[i] = cal; 583820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 584820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 585820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return true; 586820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 587820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 588820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 589820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Write out the STANDARD block of VTIMEZONE and end the VTIMEZONE 590820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param writer the SimpleIcsWriter we're using 591820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz the time zone 592820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param offsetString the offset string in VTIMEZONE format (e.g. +0800) 593820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @throws IOException 594820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 595820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank static private void writeNoDST(SimpleIcsWriter writer, TimeZone tz, String offsetString) 596820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank throws IOException { 597820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "STANDARD"); 598820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETFROM", offsetString); 599820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETTO", offsetString); 600820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Might as well use start of epoch for start date 601820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("DTSTART", millisToEasDateTime(0L)); 602820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "STANDARD"); 603820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "VTIMEZONE"); 604820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 605820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 606820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** Write a VTIMEZONE block for a given TimeZone into a SimpleIcsWriter 607820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz the TimeZone to be used in the conversion 608820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param writer the SimpleIcsWriter to be used 609820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @throws IOException 610820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 61179268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static void timeZoneToVTimezone(TimeZone tz, SimpleIcsWriter writer) 612820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank throws IOException { 613820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use these regardless of whether there's DST in this time zone or not 614820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int rawOffsetMinutes = tz.getRawOffset() / MINUTES; 615820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank String standardOffsetString = utcOffsetString(rawOffsetMinutes); 616820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 617820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Preamble for all of our VTIMEZONEs 618820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "VTIMEZONE"); 619820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZID", tz.getID()); 620820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("X-LIC-LOCATION", tz.getDisplayName()); 621820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 622820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Simplest case is no daylight time 623820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (!tz.useDaylightTime()) { 624820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writeNoDST(writer, tz, standardOffsetString); 625820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return; 626820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 627820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 628820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int maxYears = 3; 629820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toDaylightCalendars = new GregorianCalendar[maxYears]; 630820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toStandardCalendars = new GregorianCalendar[maxYears]; 631820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (!getDSTCalendars(tz, toDaylightCalendars, toStandardCalendars)) { 632820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writeNoDST(writer, tz, standardOffsetString); 633820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return; 634820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 635820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Try to find a rule to cover these yeras 636820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule daylightRule = inferRRuleFromCalendars(toDaylightCalendars); 637820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule standardRule = inferRRuleFromCalendars(toStandardCalendars); 638820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank String daylightOffsetString = 639820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank utcOffsetString(rawOffsetMinutes + (tz.getDSTSavings() / MINUTES)); 640820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We'll use RRULE's if we found both 641820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Otherwise we write the first as DTSTART and the others as RDATE 642820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank boolean hasRule = daylightRule != null && standardRule != null; 643820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 644820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write the DAYLIGHT block 645820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "DAYLIGHT"); 646820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETFROM", standardOffsetString); 647820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETTO", daylightOffsetString); 648820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("DTSTART", 64910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank transitionMillisToVCalendarTime( 65010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank toDaylightCalendars[0].getTimeInMillis(), tz, true)); 651820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hasRule) { 652820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("RRULE", daylightRule.toString()); 653820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 654820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 1; i < maxYears; i++) { 65510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank writer.writeTag("RDATE", transitionMillisToVCalendarTime( 656820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toDaylightCalendars[i].getTimeInMillis(), tz, true)); 657820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 658820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 659820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "DAYLIGHT"); 660820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write the STANDARD block 661820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("BEGIN", "STANDARD"); 662820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETFROM", daylightOffsetString); 663820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("TZOFFSETTO", standardOffsetString); 664820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("DTSTART", 66510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank transitionMillisToVCalendarTime( 66610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank toStandardCalendars[0].getTimeInMillis(), tz, false)); 667820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (hasRule) { 668820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("RRULE", standardRule.toString()); 669820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 670820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (int i = 1; i < maxYears; i++) { 67110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank writer.writeTag("RDATE", transitionMillisToVCalendarTime( 672820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank toStandardCalendars[i].getTimeInMillis(), tz, true)); 673820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 674820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 675820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "STANDARD"); 676820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // And we're done 677820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank writer.writeTag("END", "VTIMEZONE"); 678820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 679820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 680820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 681820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * Find the next transition to occur (i.e. after the current date/time) 682820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param transitions calendars representing transitions to/from DST 683820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @return millis for the first transition after the current date/time 684820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 68579268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static long findNextTransition(long startingMillis, GregorianCalendar[] transitions) { 686820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank for (GregorianCalendar transition: transitions) { 687820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long transitionMillis = transition.getTimeInMillis(); 688820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (transitionMillis > startingMillis) { 689820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return transitionMillis; 690820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 691820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 692820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return 0; 693820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 694820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 695820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 696377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Calculate the Base64 representation of a MSFT TIME_ZONE_INFORMATION structure from a TimeZone 697377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * that might be found in an Event. Since the internal representation of the TimeZone is hidden 698377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * from us we'll find the DST transitions and build the structure from that information 699377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param tz the TimeZone 700377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return the Base64 String representing a Microsoft TIME_ZONE_INFORMATION element 701377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 70279268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static String timeZoneToTziStringImpl(TimeZone tz) { 703377230593dca7cb01483bfaf93959e5821f5f028Marc Blank String tziString; 704377230593dca7cb01483bfaf93959e5821f5f028Marc Blank byte[] tziBytes = new byte[MSFT_TIME_ZONE_SIZE]; 705377230593dca7cb01483bfaf93959e5821f5f028Marc Blank int standardBias = - tz.getRawOffset(); 706377230593dca7cb01483bfaf93959e5821f5f028Marc Blank standardBias /= 60*SECONDS; 707377230593dca7cb01483bfaf93959e5821f5f028Marc Blank setLong(tziBytes, MSFT_TIME_ZONE_BIAS_OFFSET, standardBias); 708820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If this time zone has daylight savings time, we need to do more work 709377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (tz.useDaylightTime()) { 710820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toDaylightCalendars = new GregorianCalendar[3]; 711820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar[] toStandardCalendars = new GregorianCalendar[3]; 712820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // See if we can get transitions for a few years; if not, we can't generate DST info 713820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // for this time zone 714820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (getDSTCalendars(tz, toDaylightCalendars, toStandardCalendars)) { 715820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Try to find a rule to cover these years 716820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule daylightRule = inferRRuleFromCalendars(toDaylightCalendars); 717820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank RRule standardRule = inferRRuleFromCalendars(toStandardCalendars); 718820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if ((daylightRule != null) && (daylightRule.type == RRule.RRULE_DAY_WEEK) && 719820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank (standardRule != null) && (standardRule.type == RRule.RRULE_DAY_WEEK)) { 720820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // We need both rules and they have to be DAY/WEEK type 721820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write month, day of week, week, hour, minute 722820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank putRuleIntoTimeZoneInformation(tziBytes, MSFT_TIME_ZONE_STANDARD_DATE_OFFSET, 723820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank standardRule, 72410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionHour(toStandardCalendars[0]), 72510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionMinute(toStandardCalendars[0])); 726820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank putRuleIntoTimeZoneInformation(tziBytes, MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET, 727820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank daylightRule, 72810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionHour(toDaylightCalendars[0]), 72910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank getTrueTransitionMinute(toDaylightCalendars[0])); 730820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } else { 731820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If there's no rule, we'll use the first transition to standard/to daylight 732820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // And indicate that it's just for this year... 733820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long now = System.currentTimeMillis(); 734820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long standardTransition = findNextTransition(now, toStandardCalendars); 735820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank long daylightTransition = findNextTransition(now, toDaylightCalendars); 736820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If we can't find transitions, we can't do DST 737820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (standardTransition != 0 && daylightTransition != 0) { 73810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank putTransitionMillisIntoSystemTime(tziBytes, 73910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank MSFT_TIME_ZONE_STANDARD_DATE_OFFSET, standardTransition); 74010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank putTransitionMillisIntoSystemTime(tziBytes, 74110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET, daylightTransition); 742820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 743820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 744377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 745820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank int dstOffset = tz.getDSTSavings(); 746820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank setLong(tziBytes, MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET, - dstOffset / MINUTES); 747377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 7487e85a8d17b2b0c80bd04306db5a698ac3b91deaaMakoto Onuki byte[] tziEncodedBytes = Base64.encode(tziBytes, Base64.NO_WRAP); 749377230593dca7cb01483bfaf93959e5821f5f028Marc Blank tziString = new String(tziEncodedBytes); 750377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return tziString; 751377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 752377230593dca7cb01483bfaf93959e5821f5f028Marc Blank 753377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 7545862a85e17e81866ca82a9905577931947fbd44eMarc Blank * Given a String as directly read from EAS, returns a TimeZone corresponding to that String 7555862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @param timeZoneString the String read from the server 7562c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * @param precision the number of milliseconds of precision in TimeZone determination 7575862a85e17e81866ca82a9905577931947fbd44eMarc Blank * @return the TimeZone, or TimeZone.getDefault() if not found 7585862a85e17e81866ca82a9905577931947fbd44eMarc Blank */ 7592c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank @VisibleForTesting 7602c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static TimeZone tziStringToTimeZone(String timeZoneString, int precision) { 7615862a85e17e81866ca82a9905577931947fbd44eMarc Blank // If we have this time zone cached, use that value and return 7625862a85e17e81866ca82a9905577931947fbd44eMarc Blank TimeZone timeZone = sTimeZoneCache.get(timeZoneString); 7635862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (timeZone != null) { 7645862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (Eas.USER_LOG) { 7655bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank ExchangeService.log(TAG, " Using cached TimeZone " + timeZone.getID()); 766377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 767377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } else { 7682c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank timeZone = tziStringToTimeZoneImpl(timeZoneString, precision); 769377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (timeZone == null) { 770377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // If we don't find a match, we just return the current TimeZone. In theory, this 771377230593dca7cb01483bfaf93959e5821f5f028Marc Blank // shouldn't be happening... 772385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.alwaysLog("TimeZone not found using default: " + timeZoneString); 773377230593dca7cb01483bfaf93959e5821f5f028Marc Blank timeZone = TimeZone.getDefault(); 7745862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 775377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sTimeZoneCache.put(timeZoneString, timeZone); 7765862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 777377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return timeZone; 778377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 7795862a85e17e81866ca82a9905577931947fbd44eMarc Blank 780377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 7812c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * The standard entry to EAS time zone conversion, using one minute as the precision 7822c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank */ 7832c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static public TimeZone tziStringToTimeZone(String timeZoneString) { 7842c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank return tziStringToTimeZone(timeZoneString, MINUTES); 7852c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank } 7862c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank 7872c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank /** 788377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Given a String as directly read from EAS, tries to find a TimeZone in the database of all 7892c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * time zones that corresponds to that String. If the test time zone string includes DST and 7902c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * we don't find a match, and we're using standard precision, we try again with lenient 7912c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank * precision, which is a bit better than guessing 792377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param timeZoneString the String read from the server 79379268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank * @return the TimeZone, or null if not found 794377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 7952c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank static TimeZone tziStringToTimeZoneImpl(String timeZoneString, int precision) { 796377230593dca7cb01483bfaf93959e5821f5f028Marc Blank TimeZone timeZone = null; 7975862a85e17e81866ca82a9905577931947fbd44eMarc Blank // First, we need to decode the base64 string 7987e85a8d17b2b0c80bd04306db5a698ac3b91deaaMakoto Onuki byte[] timeZoneBytes = Base64.decode(timeZoneString, Base64.DEFAULT); 7995862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8005862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Then, we get the bias (similar to a rawOffset); for TimeZone, we need ms 8015862a85e17e81866ca82a9905577931947fbd44eMarc Blank // but EAS gives us minutes, so do the conversion. Note that EAS is the bias that's added 8025862a85e17e81866ca82a9905577931947fbd44eMarc Blank // to the time zone to reach UTC; our library uses the time from UTC to our time zone, so 8035862a85e17e81866ca82a9905577931947fbd44eMarc Blank // we need to change the sign 8045862a85e17e81866ca82a9905577931947fbd44eMarc Blank int bias = -1 * getLong(timeZoneBytes, MSFT_TIME_ZONE_BIAS_OFFSET) * MINUTES; 8055862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8065862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Get all of the time zones with the bias as a rawOffset; if there aren't any, we return 8075862a85e17e81866ca82a9905577931947fbd44eMarc Blank // the default time zone 8085862a85e17e81866ca82a9905577931947fbd44eMarc Blank String[] zoneIds = TimeZone.getAvailableIDs(bias); 8095862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (zoneIds.length > 0) { 8105862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Try to find an existing TimeZone from the data provided by EAS 8115862a85e17e81866ca82a9905577931947fbd44eMarc Blank // We start by pulling out the date that standard time begins 8125862a85e17e81866ca82a9905577931947fbd44eMarc Blank TimeZoneDate dstEnd = 8135862a85e17e81866ca82a9905577931947fbd44eMarc Blank getTimeZoneDateFromSystemTime(timeZoneBytes, MSFT_TIME_ZONE_STANDARD_DATE_OFFSET); 8145862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dstEnd == null) { 8155bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // If the default time zone is a match 8165bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank TimeZone defaultTimeZone = TimeZone.getDefault(); 8175bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (!defaultTimeZone.useDaylightTime() && 818b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook ArrayUtils.contains(zoneIds, defaultTimeZone.getID())) { 8195bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (Eas.USER_LOG) { 8205bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank ExchangeService.log(TAG, "TimeZone without DST found to be default: " + 8215bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank defaultTimeZone.getID()); 8225bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8235bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank return defaultTimeZone; 8245bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8255862a85e17e81866ca82a9905577931947fbd44eMarc Blank // In this case, there is no daylight savings time, so the only interesting data 8265bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // for possible matches is the offset and DST availability; we'll take the first 8275bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // match for those 8285bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank for (String zoneId: zoneIds) { 8295bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank timeZone = TimeZone.getTimeZone(zoneId); 8305bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (!timeZone.useDaylightTime()) { 8315bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank if (Eas.USER_LOG) { 8325bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank ExchangeService.log(TAG, "TimeZone without DST found by offset: " + 8335bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank timeZone.getID()); 8345bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8355bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank return timeZone; 8365bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank } 8375862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 8385bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank // None found, return null 8395bb2c4930c27c5385ff1baf033d1f1a97d770d14Marc Blank return null; 8405862a85e17e81866ca82a9905577931947fbd44eMarc Blank } else { 841377230593dca7cb01483bfaf93959e5821f5f028Marc Blank TimeZoneDate dstStart = getTimeZoneDateFromSystemTime(timeZoneBytes, 8425862a85e17e81866ca82a9905577931947fbd44eMarc Blank MSFT_TIME_ZONE_DAYLIGHT_DATE_OFFSET); 8435862a85e17e81866ca82a9905577931947fbd44eMarc Blank // See comment above for bias... 8445862a85e17e81866ca82a9905577931947fbd44eMarc Blank long dstSavings = 845377230593dca7cb01483bfaf93959e5821f5f028Marc Blank -1 * getLong(timeZoneBytes, MSFT_TIME_ZONE_DAYLIGHT_BIAS_OFFSET) * MINUTES; 8465862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8475862a85e17e81866ca82a9905577931947fbd44eMarc Blank // We'll go through each time zone to find one with the same DST transitions and 8485862a85e17e81866ca82a9905577931947fbd44eMarc Blank // savings length 8495862a85e17e81866ca82a9905577931947fbd44eMarc Blank for (String zoneId: zoneIds) { 8505862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Get the TimeZone using the zoneId 8515862a85e17e81866ca82a9905577931947fbd44eMarc Blank timeZone = TimeZone.getTimeZone(zoneId); 8525862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8535862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Our strategy here is to check just before and just after the transitions 8545862a85e17e81866ca82a9905577931947fbd44eMarc Blank // and see whether the check for daylight time matches the expectation 8555862a85e17e81866ca82a9905577931947fbd44eMarc Blank // If both transitions match, then we have a match for the offset and start/end 8565862a85e17e81866ca82a9905577931947fbd44eMarc Blank // of dst. That's the best we can do for now, since there's no other info 8575862a85e17e81866ca82a9905577931947fbd44eMarc Blank // provided by EAS (i.e. we can't get dynamic transitions, etc.) 8585862a85e17e81866ca82a9905577931947fbd44eMarc Blank 85979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Check one minute before and after DST start transition 86079268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank long millisAtTransition = getMillisAtTimeZoneDateTransition(timeZone, dstStart); 8612c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank Date before = new Date(millisAtTransition - precision); 8622c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank Date after = new Date(millisAtTransition + precision); 8635862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (timeZone.inDaylightTime(before)) continue; 8645862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (!timeZone.inDaylightTime(after)) continue; 8655862a85e17e81866ca82a9905577931947fbd44eMarc Blank 86679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Check one minute before and after DST end transition 86779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank millisAtTransition = getMillisAtTimeZoneDateTransition(timeZone, dstEnd); 86879268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Note that we need to subtract an extra hour here, because we end up with 86979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // gaining an hour in the transition BACK to standard time 8702c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank before = new Date(millisAtTransition - (dstSavings + precision)); 8712c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank after = new Date(millisAtTransition + precision); 8725862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (!timeZone.inDaylightTime(before)) continue; 8735862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (timeZone.inDaylightTime(after)) continue; 8745862a85e17e81866ca82a9905577931947fbd44eMarc Blank 8755862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Check that the savings are the same 8765862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dstSavings != timeZone.getDSTSavings()) continue; 87779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank return timeZone; 8785862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 8792c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank boolean lenient = false; 8807b873db28bda10f98f3782b7b161cb66117034d7Marc Blank boolean name = false; 8812c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank if ((dstStart.hour != dstEnd.hour) && (precision == STANDARD_DST_PRECISION)) { 8822c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank timeZone = tziStringToTimeZoneImpl(timeZoneString, LENIENT_DST_PRECISION); 8832c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank lenient = true; 8842c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank } else { 8857b873db28bda10f98f3782b7b161cb66117034d7Marc Blank // We can't find a time zone match, so our last attempt is to see if there's 8867b873db28bda10f98f3782b7b161cb66117034d7Marc Blank // a valid time zone name in the TZI; if not we'll just take the first TZ with 8877b873db28bda10f98f3782b7b161cb66117034d7Marc Blank // a matching offset (which is likely wrong, but ... what else is there to do) 8887b873db28bda10f98f3782b7b161cb66117034d7Marc Blank String tzName = getString(timeZoneBytes, MSFT_TIME_ZONE_STANDARD_NAME_OFFSET, 8897b873db28bda10f98f3782b7b161cb66117034d7Marc Blank MSFT_TIME_ZONE_STRING_SIZE); 8907b873db28bda10f98f3782b7b161cb66117034d7Marc Blank if (!tzName.isEmpty()) { 8917b873db28bda10f98f3782b7b161cb66117034d7Marc Blank TimeZone tz = TimeZone.getTimeZone(tzName); 8927b873db28bda10f98f3782b7b161cb66117034d7Marc Blank if (tz != null) { 8937b873db28bda10f98f3782b7b161cb66117034d7Marc Blank timeZone = tz; 8947b873db28bda10f98f3782b7b161cb66117034d7Marc Blank name = true; 8957b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } else { 8967b873db28bda10f98f3782b7b161cb66117034d7Marc Blank timeZone = TimeZone.getTimeZone(zoneIds[0]); 8977b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } 8987b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } else { 8997b873db28bda10f98f3782b7b161cb66117034d7Marc Blank timeZone = TimeZone.getTimeZone(zoneIds[0]); 9007b873db28bda10f98f3782b7b161cb66117034d7Marc Blank } 9012c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank } 902270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank if (Eas.USER_LOG) { 903385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.log(TAG, 9042c7d44b182654120a98921cbc864be2d135c8fdaMarc Blank "No TimeZone with correct DST settings; using " + 9057b873db28bda10f98f3782b7b161cb66117034d7Marc Blank (name ? "name" : (lenient ? "lenient" : "first")) + ": " + 9067b873db28bda10f98f3782b7b161cb66117034d7Marc Blank timeZone.getID()); 907270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank } 908270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return timeZone; 9095862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 9105862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 91179268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank return null; 9125862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 9135862a85e17e81866ca82a9905577931947fbd44eMarc Blank 9145c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank static public String convertEmailDateTimeToCalendarDateTime(String date) { 9155c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Format for email date strings is 2010-02-23T16:00:00.000Z 91679268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank // Format for calendar date strings is 20100223T160000Z 9175c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank return date.substring(0, 4) + date.substring(5, 7) + date.substring(8, 13) + 9185c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank date.substring(14, 16) + date.substring(17, 19) + 'Z'; 9195c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 9205c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 921377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static String formatTwo(int num) { 922377230593dca7cb01483bfaf93959e5821f5f028Marc Blank if (num <= 12) { 923377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return sTwoCharacterNumbers[num]; 924377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } else 925377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return Integer.toString(num); 926377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 92714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 928377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 929377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Generate an EAS formatted date/time string based on GMT. See below for details. 930377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 931377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static public String millisToEasDateTime(long millis) { 932c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank return millisToEasDateTime(millis, sGmtTimeZone, true); 933377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 9345862a85e17e81866ca82a9905577931947fbd44eMarc Blank 935377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 936e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * Generate a birthday string from a GregorianCalendar set appropriately; the format of this 937e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * string is YYYY-MM-DD 938e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * @param cal the calendar 939e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank * @return the birthday string 940e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank */ 941e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank static public String calendarToBirthdayString(GregorianCalendar cal) { 942e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank StringBuilder sb = new StringBuilder(); 943e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append(cal.get(Calendar.YEAR)); 944e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append('-'); 945e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append(formatTwo(cal.get(Calendar.MONTH) + 1)); 946e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append('-'); 947e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH))); 948e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank return sb.toString(); 949e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } 950e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank 951e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank /** 952c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * Generate an EAS formatted local date/time string from a time and a time zone. If the final 953c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * argument is false, only a date will be returned (e.g. 20100331) 954377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param millis a time in milliseconds 955377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param tz a time zone 956c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * @param withTime if the time is to be included in the string 957c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank * @return an EAS formatted string indicating the date (and time) in the given time zone 958377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 959c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank static public String millisToEasDateTime(long millis, TimeZone tz, boolean withTime) { 960377230593dca7cb01483bfaf93959e5821f5f028Marc Blank StringBuilder sb = new StringBuilder(); 961377230593dca7cb01483bfaf93959e5821f5f028Marc Blank GregorianCalendar cal = new GregorianCalendar(tz); 962377230593dca7cb01483bfaf93959e5821f5f028Marc Blank cal.setTimeInMillis(millis); 963377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sb.append(cal.get(Calendar.YEAR)); 964377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sb.append(formatTwo(cal.get(Calendar.MONTH) + 1)); 965377230593dca7cb01483bfaf93959e5821f5f028Marc Blank sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH))); 966c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (withTime) { 967c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append('T'); 968c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append(formatTwo(cal.get(Calendar.HOUR_OF_DAY))); 969c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append(formatTwo(cal.get(Calendar.MINUTE))); 970c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append(formatTwo(cal.get(Calendar.SECOND))); 971c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (tz == sGmtTimeZone) { 972c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank sb.append('Z'); 973c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank } 974820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 975820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank return sb.toString(); 976820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 977820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 978820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank /** 97910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Return the true minute at which a transition occurs 98010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Our transition time should be the in the minute BEFORE the transition 98110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * If this minute is 59, set minute to 0 and increment the hour 98210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * NOTE: We don't want to add a minute and retrieve minute/hour from the Calendar, because 98310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Calendar time will itself be influenced by the transition! So adding 1 minute to 98410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * 01:59 (assume PST->PDT) will become 03:00, which isn't what we want (we want 02:00) 98510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * 98610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @param calendar the calendar holding the transition date/time 98710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @return the true minute of the transition 98810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank */ 98979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static int getTrueTransitionMinute(GregorianCalendar calendar) { 99010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank int minute = calendar.get(Calendar.MINUTE); 99110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank if (minute == 59) { 99210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank minute = 0; 99310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 99410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank return minute; 99510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 99610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank 99710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank /** 99810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Return the true hour at which a transition occurs 99910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * See description for getTrueTransitionMinute, above 100010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @param calendar the calendar holding the transition date/time 100110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @return the true hour of the transition 100210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank */ 100379268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static int getTrueTransitionHour(GregorianCalendar calendar) { 100410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank int hour = calendar.get(Calendar.HOUR_OF_DAY); 100510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank hour++; 100610e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank if (hour == 24) { 100710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank hour = 0; 100810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 100910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank return hour; 101010e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank } 101110e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank 101210e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank /** 101310e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * Generate a date/time string suitable for VTIMEZONE from a transition time in millis 101410e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * The format is YYYYMMDDTHHMMSS 101510e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank * @param millis a transition time in milliseconds 1016820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param tz a time zone 1017820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank * @param dst whether we're entering daylight time 1018820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank */ 101979268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank static String transitionMillisToVCalendarTime(long millis, TimeZone tz, boolean dst) { 1020820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank StringBuilder sb = new StringBuilder(); 1021820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank GregorianCalendar cal = new GregorianCalendar(tz); 1022820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank cal.setTimeInMillis(millis); 1023820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(cal.get(Calendar.YEAR)); 1024820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(formatTwo(cal.get(Calendar.MONTH) + 1)); 1025820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH))); 1026820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank sb.append('T'); 102710e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank sb.append(formatTwo(getTrueTransitionHour(cal))); 102810e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank sb.append(formatTwo(getTrueTransitionMinute(cal))); 102910e1bb12c0e78b60c1302186a724e5617a2ba3bcMarc Blank sb.append(formatTwo(0)); 1030377230593dca7cb01483bfaf93959e5821f5f028Marc Blank return sb.toString(); 1031377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 10325862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1033cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank /** 1034270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * Returns a UTC calendar with year/month/day from local calendar and h/m/s/ms = 0 1035270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @param time the time in seconds of an all-day event in local time 1036270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @return the time in seconds in UTC 1037cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank */ 1038270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank static public long getUtcAllDayCalendarTime(long time, TimeZone localTimeZone) { 1039270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return transposeAllDayTime(time, localTimeZone, UTC_TIMEZONE); 1040270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank } 1041270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank 1042270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank /** 1043270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * Returns a local calendar with year/month/day from UTC calendar and h/m/s/ms = 0 1044270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @param time the time in seconds of an all-day event in UTC 1045270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank * @return the time in seconds in local time 1046270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank */ 1047270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank static public long getLocalAllDayCalendarTime(long time, TimeZone localTimeZone) { 1048270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return transposeAllDayTime(time, UTC_TIMEZONE, localTimeZone); 1049270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank } 1050270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank 1051270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank static private long transposeAllDayTime(long time, TimeZone fromTimeZone, 1052270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank TimeZone toTimeZone) { 1053270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank GregorianCalendar fromCalendar = new GregorianCalendar(fromTimeZone); 1054270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank fromCalendar.setTimeInMillis(time); 1055270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank GregorianCalendar toCalendar = new GregorianCalendar(toTimeZone); 1056cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank // Set this calendar with correct year, month, and day, but zero hour, minute, and seconds 1057270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank toCalendar.set(fromCalendar.get(GregorianCalendar.YEAR), 1058270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank fromCalendar.get(GregorianCalendar.MONTH), 1059270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank fromCalendar.get(GregorianCalendar.DATE), 0, 0, 0); 10603baaee079e644467cf18c9b250ac30485f9c54e0Marc Blank toCalendar.set(GregorianCalendar.MILLISECOND, 0); 1061270a17e49669e0bfc7bd2a6303a684a7acd1266dMarc Blank return toCalendar.getTimeInMillis(); 1062cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank } 1063cfbbe6bf8cec39204a00d31ee4277b54b0262ba6Marc Blank 1064377230593dca7cb01483bfaf93959e5821f5f028Marc Blank static void addByDay(StringBuilder rrule, int dow, int wom) { 10655862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";BYDAY="); 10665862a85e17e81866ca82a9905577931947fbd44eMarc Blank boolean addComma = false; 10675862a85e17e81866ca82a9905577931947fbd44eMarc Blank for (int i = 0; i < 7; i++) { 10685862a85e17e81866ca82a9905577931947fbd44eMarc Blank if ((dow & 1) == 1) { 10695862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (addComma) { 10705862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(','); 10715862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10725862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (wom > 0) { 10735862a85e17e81866ca82a9905577931947fbd44eMarc Blank // 5 = last week -> -1 10745862a85e17e81866ca82a9905577931947fbd44eMarc Blank // So -1SU = last sunday 10755862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(wom == 5 ? -1 : wom); 10765862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10775862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(sDayTokens[i]); 10785862a85e17e81866ca82a9905577931947fbd44eMarc Blank addComma = true; 10795862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10805862a85e17e81866ca82a9905577931947fbd44eMarc Blank dow >>= 1; 10815862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10825862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10835862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1084f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank static void addBySetpos(StringBuilder rrule, int dow, int wom) { 1085f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Indicate the days, but don't use wom in this case (it's used in the BYSETPOS); 1086f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addByDay(rrule, dow, 0); 1087f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank rrule.append(";BYSETPOS="); 1088f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank rrule.append(wom == 5 ? "-1" : wom); 1089f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1090f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank 10915862a85e17e81866ca82a9905577931947fbd44eMarc Blank static void addByMonthDay(StringBuilder rrule, int dom) { 10925862a85e17e81866ca82a9905577931947fbd44eMarc Blank // 127 means last day of the month 10935862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dom == 127) { 10945862a85e17e81866ca82a9905577931947fbd44eMarc Blank dom = -1; 10955862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10965862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";BYMONTHDAY=" + dom); 10975862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 10985862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1099f35b67cef20189c12a1a387dedf200eb30089725Marc Blank /** 1100f35b67cef20189c12a1a387dedf200eb30089725Marc Blank * Generate the String version of the EAS integer for a given BYDAY value in an rrule 1101f35b67cef20189c12a1a387dedf200eb30089725Marc Blank * @param dow the BYDAY value of the rrule 1102f35b67cef20189c12a1a387dedf200eb30089725Marc Blank * @return the String version of the EAS value of these days 1103f35b67cef20189c12a1a387dedf200eb30089725Marc Blank */ 110414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank static String generateEasDayOfWeek(String dow) { 1105f35b67cef20189c12a1a387dedf200eb30089725Marc Blank int bits = 0; 110614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int bit = 1; 110714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank for (String token: sDayTokens) { 1108f35b67cef20189c12a1a387dedf200eb30089725Marc Blank // If we can find the day in the dow String, add the bit to our bits value 1109f35b67cef20189c12a1a387dedf200eb30089725Marc Blank if (dow.indexOf(token) >= 0) { 1110f35b67cef20189c12a1a387dedf200eb30089725Marc Blank bits |= bit; 111114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1112f35b67cef20189c12a1a387dedf200eb30089725Marc Blank bit <<= 1; 111314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1114f35b67cef20189c12a1a387dedf200eb30089725Marc Blank return Integer.toString(bits); 111514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 111614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 1117377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 1118377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Extract the value of a token in an RRULE string 1119377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param rrule an RRULE string 1120377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param token a token to look for in the RRULE 1121377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return the value of that token 1122377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 112314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank static String tokenFromRrule(String rrule, String token) { 112414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int start = rrule.indexOf(token); 112514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (start < 0) return null; 112614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int len = rrule.length(); 112714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank start += token.length(); 112814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank int end = start; 112914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank char c; 113014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank do { 113114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank c = rrule.charAt(end++); 1132b129a5f1b340ae6362397685c407099ceae8d9e9Marc Blank if ((c == ';') || (end == len)) { 113314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (end == len) end++; 113414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank return rrule.substring(start, end -1); 113514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1136377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } while (true); 113714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 113814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 113914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank /** 114021c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank * Reformat an RRULE style UNTIL to an EAS style until 114121c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank */ 114260df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank @VisibleForTesting 114321c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank static String recurrenceUntilToEasUntil(String until) { 114460df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // Get a calendar in our local time zone 114560df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank GregorianCalendar localCalendar = new GregorianCalendar(TimeZone.getDefault()); 114660df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // Set the time per GMT time in the 'until' 114760df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank localCalendar.setTimeInMillis(Utility.parseDateTimeToMillis(until)); 114821c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank StringBuilder sb = new StringBuilder(); 114960df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // Build a string with local year/month/date 115060df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank sb.append(localCalendar.get(Calendar.YEAR)); 115160df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank sb.append(formatTwo(localCalendar.get(Calendar.MONTH) + 1)); 115260df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank sb.append(formatTwo(localCalendar.get(Calendar.DAY_OF_MONTH))); 115360df2ad267f1ad7aed45b583adcd1a5bb2a006b0Marc Blank // EAS ignores the time in 'until'; go figure 1154e3e9ef55a2afd2c121e1f214a8fa34fced3bac38Marc Blank sb.append("T000000Z"); 115521c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank return sb.toString(); 115621c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank } 115721c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank 115821c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank /** 1159f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank * Convenience method to add "count", "interval", and "until" to an EAS calendar stream 1160f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank * According to EAS docs, OCCURRENCES must always come before INTERVAL 116121c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank */ 1162f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank static private void addCountIntervalAndUntil(String rrule, Serializer s) throws IOException { 1163f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String count = tokenFromRrule(rrule, "COUNT="); 1164f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (count != null) { 1165f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_OCCURRENCES, count); 1166f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1167f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String interval = tokenFromRrule(rrule, "INTERVAL="); 1168f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (interval != null) { 1169f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_INTERVAL, interval); 1170f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 117121c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank String until = tokenFromRrule(rrule, "UNTIL="); 117221c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank if (until != null) { 117321c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank s.data(Tags.CALENDAR_RECURRENCE_UNTIL, recurrenceUntilToEasUntil(until)); 117421c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank } 117521c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank } 117621c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank 1177f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank static private void addByDay(String byDay, Serializer s) throws IOException { 1178f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank // This can be 1WE (1st Wednesday) or -1FR (last Friday) 1179f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank int weekOfMonth = byDay.charAt(0); 1180f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String bareByDay; 1181f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (weekOfMonth == '-') { 1182f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank // -1 is the only legal case (last week) Use "5" for EAS 1183f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank weekOfMonth = 5; 1184f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank bareByDay = byDay.substring(2); 1185f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } else { 1186f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank weekOfMonth = weekOfMonth - '0'; 1187f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank bareByDay = byDay.substring(1); 1188f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1189f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, Integer.toString(weekOfMonth)); 1190f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(bareByDay)); 1191f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1192f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank 1193f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank static private void addByDaySetpos(String byDay, String bySetpos, Serializer s) 1194f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank throws IOException { 1195f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank int weekOfMonth = bySetpos.charAt(0); 1196f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (weekOfMonth == '-') { 1197f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // -1 is the only legal case (last week) Use "5" for EAS 1198f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank weekOfMonth = 5; 1199f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1200f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank weekOfMonth = weekOfMonth - '0'; 1201f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1202f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, Integer.toString(weekOfMonth)); 1203f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(byDay)); 1204f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1205f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank 120621c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank /** 120714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * Write recurrence information to EAS based on the RRULE in CalendarProvider 120814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @param rrule the RRULE, from CalendarProvider 120914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @param startTime, the DTSTART of this Event 121014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @param s the Serializer we're using to write WBXML data 121114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank * @throws IOException 121214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank */ 121314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // NOTE: For the moment, we're only parsing recurrence types that are supported by the 1214c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank // Calendar app UI, which is a subset of possible recurrence types 121514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // This code must be updated when the Calendar adds new functionality 121614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank static public void recurrenceFromRrule(String rrule, long startTime, Serializer s) 121779268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank throws IOException { 121888683d3ebf9720ee648220dd6884e200d5e87038Marc Blank if (Eas.USER_LOG) { 1219385a0be662509754e687bcfa9813208b050bf951Marc Blank ExchangeService.log(TAG, "RRULE: " + rrule); 122088683d3ebf9720ee648220dd6884e200d5e87038Marc Blank } 122114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String freq = tokenFromRrule(rrule, "FREQ="); 122214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // If there's no FREQ=X, then we don't write a recurrence 122314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // Note that we duplicate s.start(Tags.CALENDAR_RECURRENCE); s.end(); to prevent the 122414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // possibility of writing out a partial recurrence stanza 122514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (freq != null) { 122614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (freq.equals("DAILY")) { 122714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 122814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "0"); 1229f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 123014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 123114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else if (freq.equals("WEEKLY")) { 123214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 123314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "1"); 123414045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // Requires a day of week (whereas RRULE does not) 1235f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 123614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byDay = tokenFromRrule(rrule, "BYDAY="); 123714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (byDay != null) { 123814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, generateEasDayOfWeek(byDay)); 1239f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Find week number (1-4 and 5 for last) 1240f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (byDay.startsWith("-1")) { 1241f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, "5"); 1242f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1243f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank char c = byDay.charAt(0); 1244f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (c >= '1' && c <= '4') { 1245f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_WEEKOFMONTH, byDay.substring(0, 1)); 1246f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 1247f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 124814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 124914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 125014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else if (freq.equals("MONTHLY")) { 125114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byMonthDay = tokenFromRrule(rrule, "BYMONTHDAY="); 125214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (byMonthDay != null) { 125314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 1254f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // Special case for last day of month 1255f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (byMonthDay == "-1") { 1256f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "3"); 1257f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addCountIntervalAndUntil(rrule, s); 1258f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFWEEK, "127"); 1259f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1260f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // The nth day of the month 1261f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "2"); 1262f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addCountIntervalAndUntil(rrule, s); 1263f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay); 1264f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 126514045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 126614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else { 126714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byDay = tokenFromRrule(rrule, "BYDAY="); 1268f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank String bySetpos = tokenFromRrule(rrule, "BYSETPOS="); 126914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank if (byDay != null) { 127014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.start(Tags.CALENDAR_RECURRENCE); 127114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, "3"); 1272f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 1273f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (bySetpos != null) { 1274f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addByDaySetpos(byDay, bySetpos, s); 1275f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else { 1276f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addByDay(byDay, s); 1277f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 127814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank s.end(); 127914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 128014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 128114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } else if (freq.equals("YEARLY")) { 128214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byMonth = tokenFromRrule(rrule, "BYMONTH="); 128314045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank String byMonthDay = tokenFromRrule(rrule, "BYMONTHDAY="); 1284f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank String byDay = tokenFromRrule(rrule, "BYDAY="); 1285f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (byMonth == null && byMonthDay == null) { 128614045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank // Calculate the month and day from the startDate 128714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank GregorianCalendar cal = new GregorianCalendar(); 128814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank cal.setTimeInMillis(startTime); 128914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank cal.setTimeZone(TimeZone.getDefault()); 129014045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank byMonth = Integer.toString(cal.get(Calendar.MONTH) + 1); 129114045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank byMonthDay = Integer.toString(cal.get(Calendar.DAY_OF_MONTH)); 129214045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 1293f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (byMonth != null && (byMonthDay != null || byDay != null)) { 1294f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.start(Tags.CALENDAR_RECURRENCE); 1295f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_TYPE, byDay == null ? "5" : "6"); 1296f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addCountIntervalAndUntil(rrule, s); 1297f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_MONTHOFYEAR, byMonth); 1298f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank // Note that both byMonthDay and byDay can't be true in a valid RRULE 1299f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (byMonthDay != null) { 1300f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.data(Tags.CALENDAR_RECURRENCE_DAYOFMONTH, byMonthDay); 1301f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } else { 1302f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank addByDay(byDay, s); 1303f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1304f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank s.end(); 1305f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 1306377230593dca7cb01483bfaf93959e5821f5f028Marc Blank } 130714045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 130814045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank } 130914045eac56b0aa394ce36f00b4f31dbe8bc1122dMarc Blank 1310377230593dca7cb01483bfaf93959e5821f5f028Marc Blank /** 1311377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * Build an RRULE String from EAS recurrence information 1312377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param type the type of recurrence 1313377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param occurrences how many recurrences (instances) 1314377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param interval the interval between recurrences 1315377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param dow day of the week 1316377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param dom day of the month 1317377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param wom week of the month 1318377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param moy month of the year 1319377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @param until the last recurrence time 1320377230593dca7cb01483bfaf93959e5821f5f028Marc Blank * @return a valid RRULE String 1321377230593dca7cb01483bfaf93959e5821f5f028Marc Blank */ 13225862a85e17e81866ca82a9905577931947fbd44eMarc Blank static public String rruleFromRecurrence(int type, int occurrences, int interval, int dow, 13235862a85e17e81866ca82a9905577931947fbd44eMarc Blank int dom, int wom, int moy, String until) { 13245862a85e17e81866ca82a9905577931947fbd44eMarc Blank StringBuilder rrule = new StringBuilder("FREQ=" + sTypeToFreq[type]); 13255862a85e17e81866ca82a9905577931947fbd44eMarc Blank // INTERVAL and COUNT 13265862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (occurrences > 0) { 13275862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";COUNT=" + occurrences); 13285862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 1329f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank if (interval > 0) { 1330f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank rrule.append(";INTERVAL=" + interval); 1331f72b4bc0ea714067ae528e63c916f0237d2202e6Marc Blank } 13325862a85e17e81866ca82a9905577931947fbd44eMarc Blank 13335862a85e17e81866ca82a9905577931947fbd44eMarc Blank // Days, weeks, months, etc. 13345862a85e17e81866ca82a9905577931947fbd44eMarc Blank switch(type) { 13355862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 0: // DAILY 13365862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 1: // WEEKLY 1337f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (dow > 0) addByDay(rrule, dow, wom); 13385862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13395862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 2: // MONTHLY 13405862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dom > 0) addByMonthDay(rrule, dom); 13415862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13425862a85e17e81866ca82a9905577931947fbd44eMarc Blank case 3: // MONTHLY (on the nth day) 1343f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // 127 is a special case meaning "last day of the month" 1344f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (dow == 127) { 1345f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank rrule.append(";BYMONTHDAY=-1"); 1346f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank // week 5 and dow = weekdays -> last weekday (need BYSETPOS) 1347e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } else if ((wom == 5 || wom == 1) && (dow == EAS_WEEKDAYS || dow == EAS_WEEKENDS)) { 1348f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank addBySetpos(rrule, dow, wom); 1349f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } else if (dow > 0) addByDay(rrule, dow, wom); 13505862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 1351820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank case 5: // YEARLY (specific day) 13525862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dom > 0) addByMonthDay(rrule, dom); 13535862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (moy > 0) { 13545862a85e17e81866ca82a9905577931947fbd44eMarc Blank rrule.append(";BYMONTH=" + moy); 13555862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 13565862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 1357820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank case 6: // YEARLY 13585862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (dow > 0) addByDay(rrule, dow, wom); 1359820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (dom > 0) addByMonthDay(rrule, dom); 1360820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank if (moy > 0) { 1361820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank rrule.append(";BYMONTH=" + moy); 1362820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 13635862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13645862a85e17e81866ca82a9905577931947fbd44eMarc Blank default: 13655862a85e17e81866ca82a9905577931947fbd44eMarc Blank break; 13665862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 13675862a85e17e81866ca82a9905577931947fbd44eMarc Blank 13685862a85e17e81866ca82a9905577931947fbd44eMarc Blank // UNTIL comes last 13695862a85e17e81866ca82a9905577931947fbd44eMarc Blank if (until != null) { 137021c3c670ff6b932a4ecbeda230bb160178bdd957Marc Blank rrule.append(";UNTIL=" + until); 13715862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 13725862a85e17e81866ca82a9905577931947fbd44eMarc Blank 1373f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank if (Eas.USER_LOG) { 1374f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank Log.d(Logging.LOG_TAG, "Created rrule: " + rrule); 1375f352bc9f29cacc61b195069e48d5c8b868660694Marc Blank } 13765862a85e17e81866ca82a9905577931947fbd44eMarc Blank return rrule.toString(); 13775862a85e17e81866ca82a9905577931947fbd44eMarc Blank } 137877110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank 137977110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank /** 138077110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * Create a Calendar in CalendarProvider to which synced Events will be linked 138177110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @param service the sync service requesting Calendar creation 138277110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @param account the account being synced 138377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @param mailbox the Exchange mailbox for the calendar 138477110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank * @return the unique id of the Calendar 138577110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank */ 138677110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank static public long createCalendar(EasSyncService service, Account account, Mailbox mailbox) { 138777110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // Create a Calendar object 138877110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank ContentValues cv = new ContentValues(); 138977110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // TODO How will this change if the user changes his account display name? 139004c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik cv.put(Calendars.CALENDAR_DISPLAY_NAME, account.mDisplayName); 13919e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik cv.put(Calendars.ACCOUNT_NAME, account.mEmailAddress); 13929e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik cv.put(Calendars.ACCOUNT_TYPE, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE); 139377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank cv.put(Calendars.SYNC_EVENTS, 1); 1394443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.VISIBLE, 1); 13955a02f79c91df6df44f3c95742f61f2c25c464cc3Marc Blank // Don't show attendee status if we're the organizer 1396443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.CAN_ORGANIZER_RESPOND, 0); 1397443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.CAN_MODIFY_TIME_ZONE, 0); 1398443d4f9804a32030446e7a5af7afb3c6df53736fAndy McFadden cv.put(Calendars.MAX_REMINDERS, 1); 139980a8e57ce2dc2695ed6f35599d326090e4ad9faeRoboErik cv.put(Calendars.ALLOWED_REMINDERS, ALLOWED_REMINDER_TYPES); 1400937af5abcbc1268f22a3058b00835c74ba20f116RoboErik cv.put(Calendars.ALLOWED_ATTENDEE_TYPES, ALLOWED_ATTENDEE_TYPES); 1401937af5abcbc1268f22a3058b00835c74ba20f116RoboErik cv.put(Calendars.ALLOWED_AVAILABILITY, ALLOWED_AVAILABILITIES); 14025a02f79c91df6df44f3c95742f61f2c25c464cc3Marc Blank 140377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // TODO Coordinate account colors w/ Calendar, if possible 1404d1771c4473a98c032c95ff66fa816043e08976f1Todd Kennedy int color = new AccountServiceProxy(service.mContext).getAccountColor(account.mId); 14059e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik cv.put(Calendars.CALENDAR_COLOR, color); 140604c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik cv.put(Calendars.CALENDAR_TIME_ZONE, Time.getCurrentTimezone()); 140704c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik cv.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER); 140877110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank cv.put(Calendars.OWNER_ACCOUNT, account.mEmailAddress); 140977110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank 14106989716b639d274a98141674556ac9402be32ebeRoboErik Uri uri = service.mContentResolver.insert( 14116989716b639d274a98141674556ac9402be32ebeRoboErik asSyncAdapter(Calendars.CONTENT_URI, account.mEmailAddress, 14126989716b639d274a98141674556ac9402be32ebeRoboErik Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), cv); 141377110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank // We save the id of the calendar into mSyncStatus 141477110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank if (uri != null) { 141577110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank String stringId = uri.getPathSegments().get(1); 141677110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank mailbox.mSyncStatus = stringId; 141777110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank return Long.parseLong(stringId); 141877110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank } 141977110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank return -1; 142077110d3a646dd691d84abd0b1e083385c1418ac5Marc Blank } 1421c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 14226989716b639d274a98141674556ac9402be32ebeRoboErik static Uri asSyncAdapter(Uri uri, String account, String accountType) { 14236989716b639d274a98141674556ac9402be32ebeRoboErik return uri.buildUpon() 1424693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, 1425693ed7fdd5a7ec7af87d105b76267c78a8acc3dbRoboErik "true") 14266989716b639d274a98141674556ac9402be32ebeRoboErik .appendQueryParameter(Calendars.ACCOUNT_NAME, account) 14276989716b639d274a98141674556ac9402be32ebeRoboErik .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); 14286989716b639d274a98141674556ac9402be32ebeRoboErik } 14296989716b639d274a98141674556ac9402be32ebeRoboErik 1430b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank /** 1431b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank * Return the uid for an event based on its globalObjId 1432b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank * @param globalObjId the base64 encoded String provided by EAS 1433b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank * @return the uid for the calendar event 1434b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank */ 1435b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank static public String getUidFromGlobalObjId(String globalObjId) { 1436b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank StringBuilder sb = new StringBuilder(); 1437b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // First get the decoded base64 1438b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank try { 1439b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank byte[] idBytes = Base64.decode(globalObjId, Base64.DEFAULT); 1440b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank String idString = new String(idBytes); 1441b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // If the base64 decoded string contains the magic substring: "vCal-Uid", then 1442b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // the actual uid is hidden within; the magic substring is never at the start of the 1443b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // decoded base64 1444b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank int index = idString.indexOf("vCal-Uid"); 1445b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank if (index > 0) { 1446b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // The uid starts after "vCal-Uidxxxx", where xxxx are padding 1447b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // characters. And it ends before the last character, which is ascii 0 1448b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank return idString.substring(index + 12, idString.length() - 1); 1449b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } else { 1450b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // This is an EAS uid. Go through the bytes and write out the hex 1451b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // values as characters; this is what we'll need to pass back to EAS 1452b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // when responding to the invitation 1453b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank for (byte b: idBytes) { 1454b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank Utility.byteToHex(sb, b); 1455b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1456b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank return sb.toString(); 1457b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1458b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } catch (RuntimeException e) { 1459b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank // In the worst of cases (bad format, etc.), we can always return the input 1460b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank return globalObjId; 1461b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1462b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank } 1463b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank 1464dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank /** 1465dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * Get a selfAttendeeStatus from a busy status 1466dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * The default here is NONE (i.e. we don't know the status) 1467dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * Note that a busy status of FREE must mean NONE as well, since it can't mean declined 1468dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * (there would be no event) 1469dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @param busyStatus the busy status, from EAS 1470dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @return the corresponding value for selfAttendeeStatus 1471dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank */ 1472edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank static public int attendeeStatusFromBusyStatus(int busyStatus) { 1473edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank int attendeeStatus; 1474dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank switch (busyStatus) { 1475dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case BUSY_STATUS_BUSY: 1476edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; 1477dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1478dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case BUSY_STATUS_TENTATIVE: 1479edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; 1480dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1481dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case BUSY_STATUS_FREE: 1482e51fedc3c055588a69da56d0b818ea12ed8f706fMarc Blank case BUSY_STATUS_OUT_OF_OFFICE: 1483dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank default: 1484edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; 1485dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1486edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank return attendeeStatus; 1487dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1488dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank 148965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank /** 1490f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank * Get a selfAttendeeStatus from a response type (EAS 14+) 1491f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank * The default here is NONE (i.e. we don't know the status), though in theory this can't happen 1492f9d3d43800dcb522a7c492e96d490eca9f120e43Marc Blank * @param busyStatus the response status, from EAS 149365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank * @return the corresponding value for selfAttendeeStatus 149465d022dc43e4461e86fd7bc143591f542b07428bMarc Blank */ 149565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank static public int attendeeStatusFromResponseType(int responseType) { 149665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank int attendeeStatus; 149765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank switch (responseType) { 149865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_NOT_RESPONDED: 149965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; 150065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 150165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_ACCEPTED: 150265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; 150365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 150465d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_TENTATIVE: 150565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_TENTATIVE; 150665d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 150765d022dc43e4461e86fd7bc143591f542b07428bMarc Blank case RESPONSE_TYPE_DECLINED: 150865d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_DECLINED; 150965d022dc43e4461e86fd7bc143591f542b07428bMarc Blank break; 151065d022dc43e4461e86fd7bc143591f542b07428bMarc Blank default: 151165d022dc43e4461e86fd7bc143591f542b07428bMarc Blank attendeeStatus = Attendees.ATTENDEE_STATUS_NONE; 151265d022dc43e4461e86fd7bc143591f542b07428bMarc Blank } 151365d022dc43e4461e86fd7bc143591f542b07428bMarc Blank return attendeeStatus; 151465d022dc43e4461e86fd7bc143591f542b07428bMarc Blank } 151565d022dc43e4461e86fd7bc143591f542b07428bMarc Blank 1516dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank /** Get a busy status from a selfAttendeeStatus 1517dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * The default here is BUSY 1518dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @param selfAttendeeStatus from CalendarProvider2 1519dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank * @return the corresponding value of busy status 1520dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank */ 1521edcfd554728a27a7678306ea7432bd8676ec6990Marc Blank static public int busyStatusFromAttendeeStatus(int selfAttendeeStatus) { 1522dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank int busyStatus; 1523dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank switch (selfAttendeeStatus) { 1524dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_DECLINED: 1525dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_NONE: 1526dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_INVITED: 1527dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank busyStatus = BUSY_STATUS_FREE; 1528dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1529dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_TENTATIVE: 1530dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank busyStatus = BUSY_STATUS_TENTATIVE; 1531dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1532dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank case Attendees.ATTENDEE_STATUS_ACCEPTED: 1533dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank default: 1534dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank busyStatus = BUSY_STATUS_BUSY; 1535dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank break; 1536dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1537dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank return busyStatus; 1538dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank } 1539dc27937dda50f991de9e12b98b80ee6aa3fe348eMarc Blank 1540e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank /** Get a busy status from event availability 1541e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * The default here is TENTATIVE 1542e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @param availability from CalendarProvider2 1543e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @return the corresponding value of busy status 1544e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank */ 1545e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank static public int busyStatusFromAvailability(int availability) { 1546e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank int busyStatus; 1547e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank switch (availability) { 1548e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case Events.AVAILABILITY_BUSY: 1549e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank busyStatus = BUSY_STATUS_BUSY; 1550e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1551e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case Events.AVAILABILITY_FREE: 1552e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank busyStatus = BUSY_STATUS_FREE; 1553e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1554e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case Events.AVAILABILITY_TENTATIVE: 1555e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank default: 1556e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank busyStatus = BUSY_STATUS_TENTATIVE; 1557e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1558e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1559e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank return busyStatus; 1560e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1561e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank 1562e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank /** Get an event availability from busy status 1563e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * The default here is TENTATIVE 1564e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @param busyStatus from CalendarProvider2 1565e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank * @return the corresponding availability value 1566e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank */ 1567e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank static public int availabilityFromBusyStatus(int busyStatus) { 1568e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank int availability; 1569e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank switch (busyStatus) { 1570e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case BUSY_STATUS_BUSY: 1571e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank availability = Events.AVAILABILITY_BUSY; 1572e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1573e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case BUSY_STATUS_FREE: 1574e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank availability = Events.AVAILABILITY_FREE; 1575e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1576e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank case BUSY_STATUS_TENTATIVE: 1577e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank default: 1578e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank availability = Events.AVAILABILITY_TENTATIVE; 1579e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank break; 1580e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1581e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank return availability; 1582e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank } 1583e3668322ce61d0cd8488e0c8fcdc8d067bfb769eMarc Blank 1584dafc866120dac68fabbddcc9943e3995894c5244Marc Blank static public String buildMessageTextFromEntityValues(Context context, 1585dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ContentValues entityValues, StringBuilder sb) { 1586dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (sb == null) { 1587dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb = new StringBuilder(); 1588dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1589dafc866120dac68fabbddcc9943e3995894c5244Marc Blank Resources resources = context.getResources(); 1590dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // TODO: Add more detail to message text 1591dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // Right now, we're using.. When: Tuesday, March 5th at 2:00pm 1592dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // What we're missing is the duration and any recurrence information. So this should be 1593dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // more like... When: Tuesdays, starting March 5th from 2:00pm - 3:00pm 1594dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // This would require code to build complex strings, and it will have to wait 15959ca8918b82221dd441293973ffb84d565a52993aMarc Blank // For now, we'll just use the meeting_recurring string 1596f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank 1597f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank boolean allDayEvent = false; 1598f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank if (entityValues.containsKey(Events.ALL_DAY)) { 1599f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank Integer ade = entityValues.getAsInteger(Events.ALL_DAY); 1600f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank allDayEvent = (ade != null) && (ade == 1); 1601f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank } 1602f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank boolean recurringEvent = !entityValues.containsKey(Events.ORIGINAL_SYNC_ID) && 1603f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank entityValues.containsKey(Events.RRULE); 1604f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank 1605f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank String dateTimeString; 1606f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank int res; 1607e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank long startTime = entityValues.getAsLong(Events.DTSTART); 1608f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank if (allDayEvent) { 1609e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank Date date = new Date(getLocalAllDayCalendarTime(startTime, TimeZone.getDefault())); 1610f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank dateTimeString = DateFormat.getDateInstance().format(date); 1611f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank res = recurringEvent ? R.string.meeting_allday_recurring : R.string.meeting_allday; 16129ca8918b82221dd441293973ffb84d565a52993aMarc Blank } else { 1613e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank dateTimeString = DateFormat.getDateTimeInstance().format(new Date(startTime)); 1614f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank res = recurringEvent ? R.string.meeting_recurring : R.string.meeting_when; 16159ca8918b82221dd441293973ffb84d565a52993aMarc Blank } 1616f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank sb.append(resources.getString(res, dateTimeString)); 1617f00404a18d7894ed3fba913a356a20812504dbf2Marc Blank 1618dafc866120dac68fabbddcc9943e3995894c5244Marc Blank String location = null; 1619dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (entityValues.containsKey(Events.EVENT_LOCATION)) { 1620dafc866120dac68fabbddcc9943e3995894c5244Marc Blank location = entityValues.getAsString(Events.EVENT_LOCATION); 1621efae936b117c9e4f3056d52fdbfe4d3f261483e5Marc Blank if (!TextUtils.isEmpty(location)) { 1622dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append("\n"); 1623dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append(resources.getString(R.string.meeting_where, location)); 1624dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1625dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1626dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // If there's a description for this event, append it 1627dafc866120dac68fabbddcc9943e3995894c5244Marc Blank String desc = entityValues.getAsString(Events.DESCRIPTION); 1628dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (desc != null) { 1629dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append("\n--\n"); 1630dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sb.append(desc); 1631dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1632dafc866120dac68fabbddcc9943e3995894c5244Marc Blank return sb.toString(); 1633dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1634dafc866120dac68fabbddcc9943e3995894c5244Marc Blank 1635c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank /** 163624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * Add an attendee to the ics attachment and the to list of the Message being composed 163724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param ics the ics attachment writer 163824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param toList the list of addressees for this email 163924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param attendeeName the name of the attendee 164024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param attendeeEmail the email address of the attendee 164124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param messageFlag the flag indicating the action to be indicated by the message 164224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank * @param account the sending account of the email 164324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank */ 164424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank static private void addAttendeeToMessage(SimpleIcsWriter ics, ArrayList<Address> toList, 164524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank String attendeeName, String attendeeEmail, int messageFlag, Account account) { 164624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if ((messageFlag & Message.FLAG_OUTGOING_MEETING_REQUEST_MASK) != 0) { 164724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank String icalTag = ICALENDAR_ATTENDEE_INVITE; 164824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if ((messageFlag & Message.FLAG_OUTGOING_MEETING_CANCEL) != 0) { 164924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_CANCEL; 165024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 165124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (attendeeName != null) { 165224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag += ";CN=" + SimpleIcsWriter.quoteParamValue(attendeeName); 165324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 165424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank ics.writeTag(icalTag, "MAILTO:" + attendeeEmail); 165524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank toList.add(attendeeName == null ? new Address(attendeeEmail) : 165624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank new Address(attendeeEmail, attendeeName)); 165724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } else if (attendeeEmail.equalsIgnoreCase(account.mEmailAddress)) { 165824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank String icalTag = null; 165924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank switch (messageFlag) { 166024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank case Message.FLAG_OUTGOING_MEETING_ACCEPT: 166124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_ACCEPT; 166224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank break; 166324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank case Message.FLAG_OUTGOING_MEETING_DECLINE: 166424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_DECLINE; 166524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank break; 166624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank case Message.FLAG_OUTGOING_MEETING_TENTATIVE: 166724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag = ICALENDAR_ATTENDEE_TENTATIVE; 166824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank break; 166924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 167024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (icalTag != null) { 167124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (attendeeName != null) { 167224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank icalTag += ";CN=" 167324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank + SimpleIcsWriter.quoteParamValue(attendeeName); 167424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 167524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank ics.writeTag(icalTag, "MAILTO:" + attendeeEmail); 167624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 167724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 167824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 167924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 168024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank /** 1681c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * Create a Message for an (Event) Entity 1682c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param entity the Entity for the Event (as might be retrieved by CalendarProvider) 1683c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param messageFlag the Message.FLAG_XXX constant indicating the type of email to be sent 1684c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param the unique id of this Event, or null if it can be retrieved from the Event 1685c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param the user's account 1686c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @return a Message with many fields pre-filled (more later) 1687c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank */ 1688c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank static public Message createMessageForEntity(Context context, Entity entity, 16895c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank int messageFlag, String uid, Account account) { 1690601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank return createMessageForEntity(context, entity, messageFlag, uid, account, 169124cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank null /*specifiedAttendee*/); 1692601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank } 1693601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank 1694601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank static public EmailContent.Message createMessageForEntity(Context context, Entity entity, 169524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank int messageFlag, String uid, Account account, String specifiedAttendee) { 1696c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank ContentValues entityValues = entity.getEntityValues(); 16975c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ArrayList<NamedContentValues> subValues = entity.getSubValues(); 16989e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik boolean isException = entityValues.containsKey(Events.ORIGINAL_SYNC_ID); 1699f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank boolean isReply = false; 1700c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1701c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank EmailContent.Message msg = new EmailContent.Message(); 1702c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank msg.mFlags = messageFlag; 1703c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank msg.mTimeStamp = System.currentTimeMillis(); 1704c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1705c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank String method; 1706f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if ((messageFlag & EmailContent.Message.FLAG_OUTGOING_MEETING_INVITE) != 0) { 1707c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank method = "REQUEST"; 1708f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank } else if ((messageFlag & EmailContent.Message.FLAG_OUTGOING_MEETING_CANCEL) != 0) { 1709f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank method = "CANCEL"; 1710c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } else { 1711c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank method = "REPLY"; 1712f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank isReply = true; 1713c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1714c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 17158d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank try { 17165c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Create our iCalendar writer and start generating tags 17175c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank SimpleIcsWriter ics = new SimpleIcsWriter(); 17188d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("BEGIN", "VCALENDAR"); 17198d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("METHOD", method); 17208d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("PRODID", "AndroidEmail"); 17218d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("VERSION", "2.0"); 1722820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 1723820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Our default vcalendar time zone is UTC, but this will change (below) if we're 1724820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // sending a recurring event, in which case we use local time 1725820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank TimeZone vCalendarTimeZone = sGmtTimeZone; 1726c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank String vCalendarDateSuffix = ""; 1727c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank 1728c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // Check for all day event 1729c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank boolean allDayEvent = false; 1730c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (entityValues.containsKey(Events.ALL_DAY)) { 1731c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank Integer ade = entityValues.getAsInteger(Events.ALL_DAY); 1732c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank allDayEvent = (ade != null) && (ade == 1); 1733c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank if (allDayEvent) { 1734c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // Example: DTSTART;VALUE=DATE:20100331 (all day event) 1735c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank vCalendarDateSuffix = ";VALUE=DATE"; 1736c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank } 1737c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank } 1738820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 1739820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // If we're inviting people and the meeting is recurring, we need to send our time zone 1740c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // information and make sure to send DTSTART/DTEND in local time (unless, of course, 1741b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank // this is an all-day event). Recurring, for this purpose, includes exceptions to 1742b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank // recurring events 1743b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank if (!isReply && !allDayEvent && 1744b4f78da94bcc3af7a872b5dc195d5243a948a3c7Marc Blank (entityValues.containsKey(Events.RRULE) || 17459e86eb14c6e1f7d7730f8ca6953fdfd95fe2b143RoboErik entityValues.containsKey(Events.ORIGINAL_SYNC_ID))) { 1746820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank vCalendarTimeZone = TimeZone.getDefault(); 1747820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank // Write the VTIMEZONE block to the writer 1748820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank timeZoneToVTimezone(vCalendarTimeZone, ics); 1749c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank // Example: DTSTART;TZID=US/Pacific:20100331T124500 1750c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank vCalendarDateSuffix = ";TZID=" + vCalendarTimeZone.getID(); 1751820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank } 1752820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank 17538d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("BEGIN", "VEVENT"); 17548d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (uid == null) { 175504c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik uid = entityValues.getAsString(Events.SYNC_DATA2); 17568d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 17578d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (uid != null) { 17588d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("UID", uid); 17598d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1760c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 17615c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (entityValues.containsKey("DTSTAMP")) { 17625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("DTSTAMP", entityValues.getAsString("DTSTAMP")); 17635c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } else { 176479268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank ics.writeTag("DTSTAMP", millisToEasDateTime(System.currentTimeMillis())); 17658d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1766c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 17678d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank long startTime = entityValues.getAsLong(Events.DTSTART); 17685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (startTime != 0) { 1769c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("DTSTART" + vCalendarDateSuffix, 1770c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank millisToEasDateTime(startTime, vCalendarTimeZone, !allDayEvent)); 17715c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 1772c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1773dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // If this is an Exception, we send the recurrence-id, which is just the original 1774dafc866120dac68fabbddcc9943e3995894c5244Marc Blank // instance time 1775dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (isException) { 1776dafc866120dac68fabbddcc9943e3995894c5244Marc Blank long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 1777c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("RECURRENCE-ID" + vCalendarDateSuffix, 1778c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank millisToEasDateTime(originalTime, vCalendarTimeZone, !allDayEvent)); 1779dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1780dafc866120dac68fabbddcc9943e3995894c5244Marc Blank 17818d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (!entityValues.containsKey(Events.DURATION)) { 17828d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (entityValues.containsKey(Events.DTEND)) { 1783c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("DTEND" + vCalendarDateSuffix, 178479268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank millisToEasDateTime( 1785c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank entityValues.getAsLong(Events.DTEND), vCalendarTimeZone, 1786c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank !allDayEvent)); 17878d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 17888d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } else { 17898d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Convert this into millis and add it to DTSTART for DTEND 17908d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // We'll use 1 hour as a default 17918d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank long durationMillis = HOURS; 17928d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Duration duration = new Duration(); 17938d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank try { 17948d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank duration.parse(entityValues.getAsString(Events.DURATION)); 179589bee1e3d03b439f4084bc9962bb3cbffd0b878aMarc Blank durationMillis = duration.getMillis(); 1796e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank } catch (DateException e) { 17978d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // We'll use the default in this case 17988d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1799c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank ics.writeTag("DTEND" + vCalendarDateSuffix, 180079268e63dbda6ebc94d20e72e2bb1c245ee64678Marc Blank millisToEasDateTime( 1801c1b63a27ed05bed70f74cf33fe08f8bd1f0d745fMarc Blank startTime + durationMillis, vCalendarTimeZone, !allDayEvent)); 1802c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1803c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1804dafc866120dac68fabbddcc9943e3995894c5244Marc Blank String location = null; 18058d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (entityValues.containsKey(Events.EVENT_LOCATION)) { 1806dafc866120dac68fabbddcc9943e3995894c5244Marc Blank location = entityValues.getAsString(Events.EVENT_LOCATION); 1807dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ics.writeTag("LOCATION", location); 1808dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 1809dafc866120dac68fabbddcc9943e3995894c5244Marc Blank 181004c880a6b5ad041f172d4b1eeecc06d6a06e4141RoboErik String sequence = entityValues.getAsString(SYNC_VERSION); 1811dafc866120dac68fabbddcc9943e3995894c5244Marc Blank if (sequence == null) { 1812dafc866120dac68fabbddcc9943e3995894c5244Marc Blank sequence = "0"; 18138d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 18145c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 181546e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank // We'll use 0 to mean a meeting invitation 18165c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank int titleId = 0; 18175c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank switch (messageFlag) { 18185c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_INVITE: 181946e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank if (!sequence.equals("0")) { 1820dafc866120dac68fabbddcc9943e3995894c5244Marc Blank titleId = R.string.meeting_updated; 1821dafc866120dac68fabbddcc9943e3995894c5244Marc Blank } 18225c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 18235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_ACCEPT: 18245c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank titleId = R.string.meeting_accepted; 18255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 18265c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_DECLINE: 18275c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank titleId = R.string.meeting_declined; 18285c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 18295c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank case Message.FLAG_OUTGOING_MEETING_TENTATIVE: 18305c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank titleId = R.string.meeting_tentative; 18315c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank break; 183230d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank case Message.FLAG_OUTGOING_MEETING_CANCEL: 183330d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank titleId = R.string.meeting_canceled; 183430d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank break; 18355c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 1836dafc866120dac68fabbddcc9943e3995894c5244Marc Blank Resources resources = context.getResources(); 18378d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String title = entityValues.getAsString(Events.TITLE); 18385c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (title == null) { 18395c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank title = ""; 18408d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 18415c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("SUMMARY", title); 184246e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank // For meeting invitations just use the title 184346e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank if (titleId == 0) { 184446e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank msg.mSubject = title; 184546e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank } else { 184646e18bd76629be0835a5d5e6c839b6daac6cdfdcMarc Blank // Otherwise, use the additional text 1847dafc866120dac68fabbddcc9943e3995894c5244Marc Blank msg.mSubject = resources.getString(titleId, title); 184830d2d4ea74cfca9d27dfd495cebc8387b8f2454dMarc Blank } 184900702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank 185000702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // Build the text for the message, starting with an initial line describing the 185100702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // exception (if this is one) 185200702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank StringBuilder sb = new StringBuilder(); 1853f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if (isException && !isReply) { 185400702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // Add the line, depending on whether this is a cancellation or update 185500702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank Date date = new Date(entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME)); 185600702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank String dateString = DateFormat.getDateInstance().format(date); 185700702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank if (titleId == R.string.meeting_canceled) { 185800702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank sb.append(resources.getString(R.string.exception_cancel, dateString)); 185900702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } else { 186000702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank sb.append(resources.getString(R.string.exception_updated, dateString)); 186100702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } 186200702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank sb.append("\n\n"); 186300702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } 186400702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank String text = 186500702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank CalendarUtilities.buildMessageTextFromEntityValues(context, entityValues, sb); 186600702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank 186700702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank if (text.length() > 0) { 186800702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank ics.writeTag("DESCRIPTION", text); 186900702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank } 187000702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // And store the message text 187100702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank msg.mText = text; 1872f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if (!isReply) { 18735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (entityValues.containsKey(Events.ALL_DAY)) { 18745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Integer ade = entityValues.getAsInteger(Events.ALL_DAY); 18755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("X-MICROSOFT-CDO-ALLDAYEVENT", ade == 0 ? "FALSE" : "TRUE"); 18765c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 1877c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 18785c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank String rrule = entityValues.getAsString(Events.RRULE); 18795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank if (rrule != null) { 18805c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ics.writeTag("RRULE", rrule); 18815c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } 18825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 188300702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // If we decide to send alarm information in the meeting request ics file, 188400702b7e577ff2b7b1ae3f6dceefc615ba9cba72Marc Blank // handle it here by looping through the subvalues 1885c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1886c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1887332d08cc2fc37d9936a73e3a120125c60b0587d1Marc Blank // Handle attendee data here; determine "to" list and add ATTENDEE tags to ics 18888d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String organizerName = null; 18898d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String organizerEmail = null; 18908d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ArrayList<Address> toList = new ArrayList<Address>(); 18918d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank for (NamedContentValues ncv: subValues) { 18928d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Uri ncvUri = ncv.uri; 18938d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ContentValues ncvValues = ncv.values; 18948d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (ncvUri.equals(Attendees.CONTENT_URI)) { 18958d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Integer relationship = 18968d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ncvValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP); 18978d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // If there's no relationship, we can't create this for EAS 18988d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Similarly, we need an attendee email for each invitee 18998d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (relationship != null && 19008d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ncvValues.containsKey(Attendees.ATTENDEE_EMAIL)) { 19018d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Organizer isn't among attendees in EAS 19028d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (relationship == Attendees.RELATIONSHIP_ORGANIZER) { 19038d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank organizerName = ncvValues.getAsString(Attendees.ATTENDEE_NAME); 19048d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank organizerEmail = ncvValues.getAsString(Attendees.ATTENDEE_EMAIL); 19058d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank continue; 1906c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 19078d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String attendeeEmail = ncvValues.getAsString(Attendees.ATTENDEE_EMAIL); 19088d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String attendeeName = ncvValues.getAsString(Attendees.ATTENDEE_NAME); 190924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 19108d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // This shouldn't be possible, but allow for it 19118d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (attendeeEmail == null) continue; 191224cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank // If we only want to send to the specifiedAttendee, eliminate others here 191324cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if ((specifiedAttendee != null) && 191424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank !attendeeEmail.equalsIgnoreCase(specifiedAttendee)) { 191524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank continue; 1916c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 191724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 191824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank addAttendeeToMessage(ics, toList, attendeeName, attendeeEmail, messageFlag, 191924cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank account); 1920c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1921c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1922c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1923c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 192424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank // Manually add the specifiedAttendee if he wasn't added in the Attendees loop 192524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (toList.isEmpty() && (specifiedAttendee != null)) { 192624cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank addAttendeeToMessage(ics, toList, null, specifiedAttendee, messageFlag, account); 192724cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank } 192824cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank 19298d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Create the organizer tag for ical 19308d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (organizerEmail != null) { 19318d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank String icalTag = "ORGANIZER"; 19328d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // We should be able to find this, assuming the Email is the user's email 19338d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // TODO Find this in the account 19348d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank if (organizerName != null) { 1935bc0c8c1523fc0bc42eb82dba6c2977492e441c03Makoto Onuki icalTag += ";CN=" + SimpleIcsWriter.quoteParamValue(organizerName); 19368d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 19378d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag(icalTag, "MAILTO:" + organizerEmail); 1938f58e3ba6e6e246a804e6908c831a43b46a61bc07Marc Blank if (isReply) { 19398d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank toList.add(organizerName == null ? new Address(organizerEmail) : 19408d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank new Address(organizerEmail, organizerName)); 19418d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 1942c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1943c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 194424cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank // If we have no "to" list, we're done 194524cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank if (toList.isEmpty()) return null; 1946601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank 19478d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank // Write out the "to" list 19488d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank Address[] toArray = new Address[toList.size()]; 19498d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank int i = 0; 19508d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank for (Address address: toList) { 19518d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank toArray[i++] = address; 19528d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank } 19538d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank msg.mTo = Address.pack(toArray); 19548d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank 1955820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank ics.writeTag("CLASS", "PUBLIC"); 1956dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ics.writeTag("STATUS", (messageFlag == Message.FLAG_OUTGOING_MEETING_CANCEL) ? 1957dafc866120dac68fabbddcc9943e3995894c5244Marc Blank "CANCELLED" : "CONFIRMED"); 1958820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank ics.writeTag("TRANSP", "OPAQUE"); // What Exchange uses 1959820dbc5ff3497fdd98fdb1cc42c1d298f9c1f199Marc Blank ics.writeTag("PRIORITY", "5"); // 1 to 9, 5 = medium 1960dafc866120dac68fabbddcc9943e3995894c5244Marc Blank ics.writeTag("SEQUENCE", sequence); 19618d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("END", "VEVENT"); 19628d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank ics.writeTag("END", "VCALENDAR"); 19635c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 19645c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Create the ics attachment using the "content" field 19655c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Attachment att = new Attachment(); 1966bc0c8c1523fc0bc42eb82dba6c2977492e441c03Makoto Onuki att.mContentBytes = ics.getBytes(); 19675c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank att.mMimeType = "text/calendar; method=" + method; 19685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank att.mFileName = "invite.ics"; 1969e54b75dc4f638e594e9b97e3b4ed8829fbc9b521Makoto Onuki att.mSize = att.mContentBytes.length; 19705c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // We don't send content-disposition with this attachment 1971b4d217e5170ae397d741e95308d98e80d0c2f637Marc Blank att.mFlags = Attachment.FLAG_ICS_ALTERNATIVE_PART; 19725c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank 19735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank // Add the attachment to the message 19745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank msg.mAttachments = new ArrayList<Attachment>(); 19755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank msg.mAttachments.add(att); 19765c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank } catch (IOException e) { 19775c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank Log.w(TAG, "IOException in createMessageForEntity"); 19788d5c79fe3d792065b72edb6231d51e4301fd2bccMarc Blank return null; 1979c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1980c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1981c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank // Return the new Message to caller 1982c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank return msg; 1983c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 1984c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank 1985c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank /** 19866989716b639d274a98141674556ac9402be32ebeRoboErik * Create a Message for an Event that can be retrieved from CalendarProvider 19876989716b639d274a98141674556ac9402be32ebeRoboErik * by its unique id 19886989716b639d274a98141674556ac9402be32ebeRoboErik * 1989c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param cr a content resolver that can be used to query for the Event 1990c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param eventId the unique id of the Event 19916989716b639d274a98141674556ac9402be32ebeRoboErik * @param messageFlag the Message.FLAG_XXX constant indicating the type of 19926989716b639d274a98141674556ac9402be32ebeRoboErik * email to be sent 19936989716b639d274a98141674556ac9402be32ebeRoboErik * @param the unique id of this Event, or null if it can be retrieved from 19946989716b639d274a98141674556ac9402be32ebeRoboErik * the Event 1995c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @param the user's account 19966989716b639d274a98141674556ac9402be32ebeRoboErik * @param requireAddressees if true (the default), no Message is returned if 19976989716b639d274a98141674556ac9402be32ebeRoboErik * there aren't any addressees; if false, return the Message 19986989716b639d274a98141674556ac9402be32ebeRoboErik * regardless (addressees will be filled in later) 1999c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank * @return a Message with many fields pre-filled (more later) 20006989716b639d274a98141674556ac9402be32ebeRoboErik * @throws RemoteException if there is an issue retrieving the Event from 20016989716b639d274a98141674556ac9402be32ebeRoboErik * CalendarProvider 2002c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank */ 20035c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank static public EmailContent.Message createMessageForEventId(Context context, long eventId, 2004c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank int messageFlag, String uid, Account account) throws RemoteException { 2005601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank return createMessageForEventId(context, eventId, messageFlag, uid, account, 20066989716b639d274a98141674556ac9402be32ebeRoboErik null /* specifiedAttendee */); 2007601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank } 2008601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank 2009601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank static public EmailContent.Message createMessageForEventId(Context context, long eventId, 201024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank int messageFlag, String uid, Account account, String specifiedAttendee) 2011601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank throws RemoteException { 20125c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank ContentResolver cr = context.getContentResolver(); 20136989716b639d274a98141674556ac9402be32ebeRoboErik EntityIterator eventIterator = EventsEntity.newEntityIterator(cr.query( 20146989716b639d274a98141674556ac9402be32ebeRoboErik ContentUris.withAppendedId(Events.CONTENT_URI, eventId), null, null, null, null), 20156989716b639d274a98141674556ac9402be32ebeRoboErik cr); 2016c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank try { 2017c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank while (eventIterator.hasNext()) { 2018c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank Entity entity = eventIterator.next(); 2019601273ad3ff202f50c17061bd2a8fe9492850802Marc Blank return createMessageForEntity(context, entity, messageFlag, uid, account, 202024cce3b10f1b3ed69de7c4d693e944cab05f8ad2Marc Blank specifiedAttendee); 2021c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2022c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } finally { 2023c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank eventIterator.close(); 2024c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2025c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank return null; 2026c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank } 2027393208ab154d18a876842777781ab153d34a0281Marc Blank 2028393208ab154d18a876842777781ab153d34a0281Marc Blank /** 2029393208ab154d18a876842777781ab153d34a0281Marc Blank * Return a boolean value for an integer ContentValues column 2030393208ab154d18a876842777781ab153d34a0281Marc Blank * @param values a ContentValues object 2031393208ab154d18a876842777781ab153d34a0281Marc Blank * @param columnName the name of a column to be found in the ContentValues 2032393208ab154d18a876842777781ab153d34a0281Marc Blank * @return a boolean representation of the value of columnName in values; null and 0 = false, 2033393208ab154d18a876842777781ab153d34a0281Marc Blank * other integers = true 2034393208ab154d18a876842777781ab153d34a0281Marc Blank */ 2035393208ab154d18a876842777781ab153d34a0281Marc Blank static public boolean getIntegerValueAsBoolean(ContentValues values, String columnName) { 2036393208ab154d18a876842777781ab153d34a0281Marc Blank Integer intValue = values.getAsInteger(columnName); 2037393208ab154d18a876842777781ab153d34a0281Marc Blank return (intValue != null && intValue != 0); 2038393208ab154d18a876842777781ab153d34a0281Marc Blank } 20398e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda} 2040