File indexing completed on 2024-11-24 04:42:06
0001 /* 0002 SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org> 0003 SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 package org.kde.kcalendarcore; 0007 0008 import android.content.*; 0009 import android.database.*; 0010 import android.net.Uri; 0011 import android.provider.*; 0012 import android.util.Log; 0013 0014 import java.util.HashSet; 0015 0016 public class Calendar 0017 { 0018 public Calendar(android.content.Context context, long id) 0019 { 0020 m_context = context; 0021 m_id = id; 0022 } 0023 0024 private static final String TAG = "org.kde.kcalendarcore"; 0025 0026 // keep same field order as in INSTANCE_PROJECTION! 0027 private static final String[] EVENT_PROJECTION = new String[] { 0028 CalendarContract.Events._ID, 0029 CalendarContract.Events.ORGANIZER, 0030 CalendarContract.Events.TITLE, 0031 CalendarContract.Events.EVENT_LOCATION, 0032 CalendarContract.Events.DESCRIPTION, 0033 //CalendarContract.Events.EVENT_COLOR 0034 CalendarContract.Events.DTSTART, 0035 CalendarContract.Events.DTEND, 0036 CalendarContract.Events.EVENT_TIMEZONE, 0037 CalendarContract.Events.EVENT_END_TIMEZONE, 0038 CalendarContract.Events.DURATION, 0039 CalendarContract.Events.ALL_DAY, 0040 CalendarContract.Events.RRULE, 0041 CalendarContract.Events.RDATE, 0042 CalendarContract.Events.EXRULE, 0043 CalendarContract.Events.EXDATE, 0044 CalendarContract.Events.ORIGINAL_ID, 0045 CalendarContract.Events.ORIGINAL_INSTANCE_TIME, 0046 //CalendarContract.Events.ORIGINAL_ALL_DAY 0047 CalendarContract.Events.ACCESS_LEVEL, 0048 CalendarContract.Events.AVAILABILITY, 0049 //CalendarContract.Events.CUSTOM_APP_PACKAGE, 0050 //CalendarContract.Events.CUSTOM_APP_URI, 0051 CalendarContract.Events.UID_2445 0052 }; 0053 0054 private static final String[] ATTENDEE_PROJECTION = new String[] { 0055 CalendarContract.Attendees.ATTENDEE_NAME, 0056 CalendarContract.Attendees.ATTENDEE_EMAIL, 0057 CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, 0058 CalendarContract.Attendees.ATTENDEE_TYPE, 0059 CalendarContract.Attendees.ATTENDEE_STATUS 0060 }; 0061 0062 private static final String[] EXTENDED_PROPERTY_PROJECTION = new String[] { 0063 CalendarContract.ExtendedProperties.NAME, 0064 CalendarContract.ExtendedProperties.VALUE 0065 }; 0066 0067 private static final String[] REMINDER_PROJECTION = new String[] { 0068 CalendarContract.Reminders.MINUTES, 0069 CalendarContract.Reminders.METHOD 0070 }; 0071 0072 // keep same field order as in EVENT_PROJECTION! 0073 private static final String[] INSTANCE_PROJECTION = new String[] { 0074 CalendarContract.Instances.EVENT_ID, 0075 CalendarContract.Instances.ORGANIZER, 0076 CalendarContract.Instances.TITLE, 0077 CalendarContract.Instances.EVENT_LOCATION, 0078 CalendarContract.Instances.DESCRIPTION, 0079 //CalendarContract.Instances.EVENT_COLOR 0080 CalendarContract.Instances.DTSTART, 0081 CalendarContract.Instances.DTEND, 0082 CalendarContract.Instances.EVENT_TIMEZONE, 0083 CalendarContract.Instances.EVENT_END_TIMEZONE, 0084 CalendarContract.Instances.DURATION, 0085 CalendarContract.Instances.ALL_DAY, 0086 CalendarContract.Instances.RRULE, 0087 CalendarContract.Instances.RDATE, 0088 CalendarContract.Instances.EXRULE, 0089 CalendarContract.Instances.EXDATE, 0090 CalendarContract.Instances.ORIGINAL_ID, 0091 CalendarContract.Instances.ORIGINAL_INSTANCE_TIME, 0092 //CalendarContract.Instances.ORIGINAL_ALL_DAY 0093 CalendarContract.Instances.ACCESS_LEVEL, 0094 CalendarContract.Instances.AVAILABILITY, 0095 //CalendarContract.Instances.CUSTOM_APP_PACKAGE, 0096 //CalendarContract.Instances.CUSTOM_APP_URI, 0097 CalendarContract.Instances.UID_2445 0098 }; 0099 0100 public EventData[] rawEvents() 0101 { 0102 ContentResolver cr = m_context.getContentResolver(); 0103 0104 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ))"; 0105 Cursor cur = cr.query(CalendarContract.Events.CONTENT_URI, EVENT_PROJECTION, eventSelection, null, null); 0106 0107 EventData result[] = new EventData[cur.getCount()]; 0108 while (cur.moveToNext()) { 0109 result[cur.getPosition()] = eventFromCurser(cur); 0110 } 0111 0112 for (int i = 0; i < result.length; ++i) { 0113 loadReminders(result[i]); 0114 loadAttendees(result[i]); 0115 loadExtendedProperties(result[i]); 0116 } 0117 0118 return result; 0119 } 0120 0121 public EventData[] rawEvents(long begin, long end, boolean inclusive) 0122 { 0123 ContentResolver cr = m_context.getContentResolver(); 0124 0125 Uri.Builder builder = CalendarContract.Instances.CONTENT_URI.buildUpon(); 0126 ContentUris.appendId(builder, begin); 0127 ContentUris.appendId(builder, end); 0128 // TODO ^ is this inclusive or exclusive? 0129 0130 String instanceSelection = "(( " + CalendarContract.Instances.CALENDAR_ID + " == " + m_id + " ))"; 0131 Cursor cur = cr.query(builder.build(), INSTANCE_PROJECTION, instanceSelection, null, null); 0132 0133 Log.i(TAG, "Events in range: " + cur.getCount()); 0134 HashSet<EventData> events = new HashSet<EventData>(); 0135 while (cur.moveToNext()) { 0136 events.add(eventFromCurser(cur)); 0137 } 0138 Log.i(TAG, "Unique events in range: " + events.size()); 0139 0140 EventData result[] = new EventData[events.size()]; 0141 int i = 0; 0142 for (EventData data : events) { 0143 loadReminders(data); 0144 loadAttendees(data); 0145 loadExtendedProperties(data); 0146 result[i++] = data; 0147 } 0148 0149 return result; 0150 } 0151 0152 public boolean addEvent(EventData event) 0153 { 0154 Log.i(TAG, event.title + " " + event.allDay + " " + event.dtStart); 0155 ContentResolver cr = m_context.getContentResolver(); 0156 ContentValues values = fillContentValues(event); 0157 // identification/add-only properties update isn't allowed to change 0158 if (event.originalId != null && !event.originalId.isEmpty()) { 0159 // TODO what is ORIGINAL_ID here? 0160 values.put(CalendarContract.Events.ORIGINAL_INSTANCE_TIME, event.instanceId); 0161 } 0162 values.put(CalendarContract.Events.CALENDAR_ID, m_id); 0163 values.put(CalendarContract.Events.UID_2445, event.uid2445); 0164 0165 Uri uri = cr.insert(CalendarContract.Events.CONTENT_URI, values); 0166 long id = Long.parseLong(uri.getLastPathSegment()); 0167 Log.i(TAG, "Event added: " + id); 0168 0169 // insert reminders, attendees and extended properties 0170 if (event.reminders != null) { 0171 for (ReminderData data : event.reminders) { 0172 addReminder(data, id); 0173 } 0174 } 0175 if (event.attendees != null) { 0176 for (AttendeeData data : event.attendees) { 0177 addAttendee(data, id); 0178 } 0179 } 0180 /* ### not allowed for non-syncproviders :( 0181 if (event.extendedProperties != null) { 0182 for (ExtendedPropertyData data : event.extendedProperties) { 0183 addExtendedProperty(data, id); 0184 } 0185 }*/ 0186 0187 return true; 0188 } 0189 0190 public EventData event(java.lang.String uid) 0191 { 0192 ContentResolver cr = m_context.getContentResolver(); 0193 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0194 + CalendarContract.Events.UID_2445 + " == ? ) AND (" 0195 + CalendarContract.Events.ORIGINAL_ID + " is null))"; 0196 String[] selectionArgs = new String[] { uid }; 0197 Cursor cur = cr.query(CalendarContract.Events.CONTENT_URI, EVENT_PROJECTION, eventSelection, selectionArgs, null); 0198 if (cur.moveToNext()) { 0199 EventData result = eventFromCurser(cur); 0200 loadReminders(result); 0201 loadAttendees(result); 0202 loadExtendedProperties(result); 0203 return result; 0204 } 0205 return null; 0206 } 0207 0208 public EventData event(java.lang.String uid, long instanceId) 0209 { 0210 ContentResolver cr = m_context.getContentResolver(); 0211 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0212 + CalendarContract.Events.UID_2445 + " == ? ) AND (" 0213 + CalendarContract.Events.ORIGINAL_INSTANCE_TIME + " == " + instanceId + " ))"; 0214 String[] selectionArgs = new String[] { uid }; 0215 Cursor cur = cr.query(CalendarContract.Events.CONTENT_URI, EVENT_PROJECTION, eventSelection, selectionArgs, null); 0216 if (cur.moveToNext()) { 0217 EventData result = eventFromCurser(cur); 0218 loadReminders(result); 0219 loadAttendees(result); 0220 loadExtendedProperties(result); 0221 return result; 0222 } 0223 return null; 0224 } 0225 0226 public boolean deleteEvent(java.lang.String uid) 0227 { 0228 ContentResolver cr = m_context.getContentResolver(); 0229 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0230 + CalendarContract.Events.UID_2445 + " == ? ) AND (" 0231 + CalendarContract.Events.ORIGINAL_ID + " is null ))"; 0232 String[] selectionArgs = new String[] { uid }; 0233 return cr.delete(CalendarContract.Events.CONTENT_URI, eventSelection, selectionArgs) > 0; 0234 } 0235 0236 public boolean deleteEvent(java.lang.String uid, long instanceId) 0237 { 0238 ContentResolver cr = m_context.getContentResolver(); 0239 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0240 + CalendarContract.Events.UID_2445 + " == ? ) AND (" 0241 + CalendarContract.Events.ORIGINAL_INSTANCE_TIME + " == " + instanceId + " ))"; 0242 String[] selectionArgs = new String[] { uid }; 0243 return cr.delete(CalendarContract.Events.CONTENT_URI, eventSelection, selectionArgs) > 0; 0244 } 0245 0246 public boolean deleteEventInstances(java.lang.String uid) 0247 { 0248 ContentResolver cr = m_context.getContentResolver(); 0249 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0250 + CalendarContract.Events.UID_2445 + " == ? ))"; 0251 String[] selectionArgs = new String[] { uid }; 0252 return cr.delete(CalendarContract.Events.CONTENT_URI, eventSelection, selectionArgs) > 0; 0253 } 0254 0255 public EventData[] eventInstances(java.lang.String uid) 0256 { 0257 ContentResolver cr = m_context.getContentResolver(); 0258 0259 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0260 + CalendarContract.Events.UID_2445 + " == ? ))"; 0261 String[] selectionArgs = new String[] { uid }; 0262 0263 Cursor cur = cr.query(CalendarContract.Events.CONTENT_URI, EVENT_PROJECTION, eventSelection, selectionArgs, null); 0264 EventData result[] = new EventData[cur.getCount()]; 0265 while (cur.moveToNext()) { 0266 result[cur.getPosition()] = eventFromCurser(cur); 0267 } 0268 0269 for (int i = 0; i < result.length; ++i) { 0270 loadReminders(result[i]); 0271 loadAttendees(result[i]); 0272 loadExtendedProperties(result[i]); 0273 } 0274 0275 return result; 0276 } 0277 0278 public boolean updateEvent(EventData event, boolean remindersChanged, boolean attendeesChanged) 0279 { 0280 ContentResolver cr = m_context.getContentResolver(); 0281 0282 // determine event id 0283 String eventSelection = "(( " + CalendarContract.Events.CALENDAR_ID + " == " + m_id + " ) AND ( " 0284 + CalendarContract.Events.UID_2445 + " == ? ) AND ("; 0285 if (event.originalId == null || event.originalId.isEmpty()) { 0286 eventSelection += CalendarContract.Events.ORIGINAL_ID + " is null ))"; 0287 } else { 0288 eventSelection += CalendarContract.Events.ORIGINAL_INSTANCE_TIME + " == " + event.instanceId + " ))"; 0289 } 0290 String[] selectionArgs = new String[] { event.uid2445 }; 0291 Cursor cur = cr.query(CalendarContract.Events.CONTENT_URI, EVENT_PROJECTION, eventSelection, selectionArgs, null); 0292 if (cur.getCount() != 1 || !cur.moveToNext()) { 0293 Log.w(TAG, "unable to identify event to update: " + cur.getCount()); 0294 return false; 0295 } 0296 long eventId = cur.getLong(0); 0297 Log.d(TAG, "found event to update: " + eventId); 0298 0299 // apply event changes 0300 ContentValues values = fillContentValues(event); 0301 int rows = cr.update(ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId), values, null, null); 0302 if (rows != 1) { 0303 Log.w(TAG, "unable to update event: " + rows); 0304 return false; 0305 } 0306 0307 // apply reminder changes 0308 if (remindersChanged) { 0309 deleteReminders(eventId); 0310 if (event.reminders != null) { 0311 for (ReminderData data : event.reminders) { 0312 addReminder(data, eventId); 0313 } 0314 } 0315 } 0316 0317 // apply attendee changes 0318 if (attendeesChanged) { 0319 deleteAttendees(eventId); 0320 if (event.attendees != null) { 0321 for (AttendeeData data : event.attendees) { 0322 addAttendee(data, eventId); 0323 } 0324 } 0325 } 0326 0327 return true; 0328 } 0329 0330 private EventData eventFromCurser(Cursor c) 0331 { 0332 EventData data = new EventData(); 0333 data.id = c.getLong(0); 0334 data.organizer = c.getString(1); 0335 data.title = c.getString(2); 0336 data.location = c.getString(3); 0337 data.description = c.getString(4); 0338 data.dtStart = c.getLong(5); 0339 data.dtEnd = c.getLong(6); 0340 data.startTimezone = c.getString(7); 0341 data.endTimezone = c.getString(8); 0342 data.duration = c.getString(9); 0343 data.allDay = c.getInt(10) == 1; 0344 data.rrule = c.getString(11); 0345 data.rdate = c.getString(12); 0346 data.exrule = c.getString(13); 0347 data.exdate = c.getString(14); 0348 data.originalId = c.getString(15); 0349 data.instanceId = c.getLong(16); 0350 data.accessLevel = c.getInt(17); 0351 data.availability = c.getInt(18); 0352 data.uid2445 = c.getString(19); 0353 return data; 0354 } 0355 0356 private void loadReminders(EventData event) 0357 { 0358 ContentResolver cr = m_context.getContentResolver(); 0359 0360 String reminderSelection = "(( " + CalendarContract.Reminders.EVENT_ID + " == " + event.id + " ))"; 0361 Cursor cur = cr.query(CalendarContract.Reminders.CONTENT_URI, REMINDER_PROJECTION, reminderSelection, null, null); 0362 0363 int reminderCount = cur.getCount(); 0364 if (reminderCount <= 0) { 0365 return; 0366 } 0367 0368 event.reminders = new ReminderData[reminderCount]; 0369 while (cur.moveToNext()) { 0370 ReminderData data = new ReminderData(); 0371 data.minutes = cur.getInt(0); 0372 data.method = cur.getInt(1); 0373 event.reminders[cur.getPosition()] = data; 0374 } 0375 } 0376 0377 private void loadAttendees(EventData event) 0378 { 0379 ContentResolver cr = m_context.getContentResolver(); 0380 0381 String attendeeSelection = "(( " + CalendarContract.Attendees.EVENT_ID + " == " + event.id + " ))"; 0382 Cursor cur = cr.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeeSelection, null, null); 0383 0384 int attendeeCount = cur.getCount(); 0385 if (attendeeCount <= 0) { 0386 return; 0387 } 0388 0389 event.attendees = new AttendeeData[attendeeCount]; 0390 while (cur.moveToNext()) { 0391 AttendeeData data = new AttendeeData(); 0392 data.name = cur.getString(0); 0393 data.email = cur.getString(1); 0394 data.relationship = cur.getInt(2); 0395 data.type = cur.getInt(3); 0396 data.status = cur.getInt(4); 0397 event.attendees[cur.getPosition()] = data; 0398 } 0399 } 0400 0401 private void loadExtendedProperties(EventData event) 0402 { 0403 ContentResolver cr = m_context.getContentResolver(); 0404 0405 String propSelection = "(( " + CalendarContract.ExtendedProperties.EVENT_ID + " == " + event.id + " ))"; 0406 Cursor cur = cr.query(CalendarContract.ExtendedProperties.CONTENT_URI, EXTENDED_PROPERTY_PROJECTION, propSelection, null, null); 0407 0408 int propCount = cur.getCount(); 0409 if (propCount <= 0) { 0410 return; 0411 } 0412 0413 event.extendedProperties = new ExtendedPropertyData[propCount]; 0414 while (cur.moveToNext()) { 0415 ExtendedPropertyData data = new ExtendedPropertyData(); 0416 data.name = cur.getString(0); 0417 data.value = cur.getString(1); 0418 event.extendedProperties[cur.getPosition()] = data; 0419 } 0420 } 0421 0422 private ContentValues fillContentValues(EventData event) 0423 { 0424 ContentValues values = new ContentValues(); 0425 values.put(CalendarContract.Events.ORGANIZER, event.organizer); 0426 values.put(CalendarContract.Events.TITLE, event.title); 0427 values.put(CalendarContract.Events.EVENT_LOCATION, event.location); 0428 values.put(CalendarContract.Events.DESCRIPTION, event.description); 0429 values.put(CalendarContract.Events.DTSTART, event.dtStart); 0430 values.put(CalendarContract.Events.DTEND, event.dtEnd); 0431 values.put(CalendarContract.Events.EVENT_TIMEZONE, event.startTimezone); 0432 values.put(CalendarContract.Events.EVENT_END_TIMEZONE, event.endTimezone); 0433 values.put(CalendarContract.Events.DURATION, event.duration); 0434 values.put(CalendarContract.Events.ALL_DAY, event.allDay ? 1 : 0); 0435 values.put(CalendarContract.Events.RRULE, event.rrule); 0436 values.put(CalendarContract.Events.RDATE, event.rdate); 0437 values.put(CalendarContract.Events.EXRULE, event.exrule); 0438 values.put(CalendarContract.Events.EXDATE, event.exdate); 0439 values.put(CalendarContract.Events.ACCESS_LEVEL, event.accessLevel); 0440 values.put(CalendarContract.Events.AVAILABILITY, event.availability); 0441 values.put(CalendarContract.Events.GUESTS_CAN_MODIFY, 1); 0442 return values; 0443 } 0444 0445 private void addReminder(ReminderData data, long eventId) 0446 { 0447 ContentResolver cr = m_context.getContentResolver(); 0448 ContentValues values = new ContentValues(); 0449 values.put(CalendarContract.Reminders.MINUTES, data.minutes); 0450 values.put(CalendarContract.Reminders.METHOD, data.method); 0451 values.put(CalendarContract.Reminders.EVENT_ID, eventId); 0452 cr.insert(CalendarContract.ExtendedProperties.CONTENT_URI, values); 0453 } 0454 0455 private void addAttendee(AttendeeData data, long eventId) 0456 { 0457 ContentResolver cr = m_context.getContentResolver(); 0458 ContentValues values = new ContentValues(); 0459 values.put(CalendarContract.Attendees.ATTENDEE_NAME, data.name); 0460 values.put(CalendarContract.Attendees.ATTENDEE_EMAIL, data.email); 0461 values.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, data.relationship); 0462 values.put(CalendarContract.Attendees.ATTENDEE_TYPE, data.type); 0463 values.put(CalendarContract.Attendees.ATTENDEE_STATUS, data.status); 0464 values.put(CalendarContract.Attendees.EVENT_ID, eventId); 0465 cr.insert(CalendarContract.Attendees.CONTENT_URI, values); 0466 } 0467 0468 private void addExtendedProperty(ExtendedPropertyData data, long eventId) 0469 { 0470 ContentResolver cr = m_context.getContentResolver(); 0471 ContentValues values = new ContentValues(); 0472 values.put(CalendarContract.ExtendedProperties.NAME, data.name); 0473 values.put(CalendarContract.ExtendedProperties.VALUE, data.value); 0474 values.put(CalendarContract.ExtendedProperties.EVENT_ID, eventId); 0475 cr.insert(CalendarContract.ExtendedProperties.CONTENT_URI, values); 0476 } 0477 0478 private void deleteReminders(long eventId) 0479 { 0480 ContentResolver cr = m_context.getContentResolver(); 0481 String reminderSelection = "(( " + CalendarContract.Reminders.EVENT_ID + " == " + eventId + " ))"; 0482 cr.delete(CalendarContract.Reminders.CONTENT_URI, reminderSelection, null); 0483 } 0484 0485 private void deleteAttendees(long eventId) 0486 { 0487 ContentResolver cr = m_context.getContentResolver(); 0488 String attendeeSelection = "(( " + CalendarContract.Attendees.EVENT_ID + " == " + eventId + " ))"; 0489 cr.delete(CalendarContract.Attendees.CONTENT_URI, attendeeSelection, null); 0490 } 0491 0492 private android.content.Context m_context; 0493 private long m_id; 0494 }