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 }