From 70f7f269446d2859def819bfcf8ff83a1af91dae Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Mon, 7 Mar 2022 10:45:09 +0100 Subject: [PATCH 1/5] added posibility to set accountname for android --- .../nl/xservices/plugins/Calendar.java | 233 +-- .../accessor/AbstractCalendarAccessor.java | 1463 +++++++++-------- 2 files changed, 872 insertions(+), 824 deletions(-) diff --git a/src/android/nl/xservices/plugins/Calendar.java b/src/android/nl/xservices/plugins/Calendar.java index bf1aa2e7..fb6cde66 100644 --- a/src/android/nl/xservices/plugins/Calendar.java +++ b/src/android/nl/xservices/plugins/Calendar.java @@ -139,17 +139,17 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo private void hasReadPermission() { this.callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, - calendarPermissionGranted(Manifest.permission.READ_CALENDAR))); + calendarPermissionGranted(Manifest.permission.READ_CALENDAR))); } private void hasWritePermission() { this.callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, - calendarPermissionGranted(Manifest.permission.WRITE_CALENDAR))); + calendarPermissionGranted(Manifest.permission.WRITE_CALENDAR))); } private void hasReadWritePermission() { this.callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, - calendarPermissionGranted(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR))); + calendarPermissionGranted(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR))); } private void requestReadPermission(int requestCode) { @@ -182,7 +182,8 @@ private void requestPermission(int requestCode, String... types) { } } - public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { + public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) + throws JSONException { for (int r : grantResults) { if (r == PackageManager.PERMISSION_DENIED) { Log.d(LOG_TAG, "Permission Denied!"); @@ -257,8 +258,10 @@ public void run() { } private void listCalendars() { - // note that if the dev didn't call requestReadPermission before calling this method and calendarPermissionGranted returns false, - // the app will ask permission and this method needs to be invoked again (done for backward compat). + // note that if the dev didn't call requestReadPermission before calling this + // method and calendarPermissionGranted returns false, + // the app will ask permission and this method needs to be invoked again (done + // for backward compat). if (!calendarPermissionGranted(Manifest.permission.READ_CALENDAR)) { requestReadPermission(PERMISSION_REQCODE_LIST_CALENDARS); return; @@ -276,9 +279,9 @@ public void run() { System.err.println("JSONException: " + e.getMessage()); callback.error(e.getMessage()); } catch (Exception ex) { - System.err.println("Exception: " + ex.getMessage()); - callback.error(ex.getMessage()); - } + System.err.println("Exception: " + ex.getMessage()); + callback.error(ex.getMessage()); + } } }); } @@ -296,6 +299,7 @@ private void createCalendar(JSONArray args) { try { final JSONObject jsonFilter = args.getJSONObject(0); + final String accountName = getPossibleNullString("accountName", jsonFilter); final String calendarColor = getPossibleNullString("calendarColor", jsonFilter); final String calendarName = getPossibleNullString("calendarName", jsonFilter); if (calendarName == null) { @@ -306,7 +310,7 @@ private void createCalendar(JSONArray args) { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { - String createdId = getCalendarAccessor().createCalendar(calendarName, calendarColor); + String createdId = getCalendarAccessor().createCalendar(accountName, calendarName, calendarColor); callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, createdId)); } }); @@ -405,15 +409,17 @@ public void run() { calIntent.putExtra("description", description); calIntent.putExtra("calendar_id", argOptionsObject.optInt("calendarId", 1)); - //set recurrence + // set recurrence String recurrence = getPossibleNullString("recurrence", argOptionsObject); - Long recurrenceEndTime = argOptionsObject.isNull("recurrenceEndTime") ? null : argOptionsObject.optLong("recurrenceEndTime"); + Long recurrenceEndTime = argOptionsObject.isNull("recurrenceEndTime") ? null + : argOptionsObject.optLong("recurrenceEndTime"); int recurrenceInterval = argOptionsObject.optInt("recurrenceInterval"); if (recurrence != null) { if (recurrenceEndTime == null) { calIntent.putExtra(Events.RRULE, "FREQ=" + recurrence.toUpperCase() + ";INTERVAL=" + recurrenceInterval); } else { - calIntent.putExtra(Events.RRULE, "FREQ=" + recurrence.toUpperCase() + ";INTERVAL=" + recurrenceInterval + ";UNTIL=" + formatICalDateTime(new Date(recurrenceEndTime))); + calIntent.putExtra(Events.RRULE, "FREQ=" + recurrence.toUpperCase() + ";INTERVAL=" + recurrenceInterval + + ";UNTIL=" + formatICalDateTime(new Date(recurrenceEndTime))); } } @@ -430,7 +436,8 @@ public void run() { private AbstractCalendarAccessor getCalendarAccessor() { if (this.calendarAccessor == null) { - // Note: currently LegacyCalendarAccessor is never used, see the TO-DO at the top of this class + // Note: currently LegacyCalendarAccessor is never used, see the TO-DO at the + // top of this class if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { Log.d(LOG_TAG, "Initializing calendar plugin"); this.calendarAccessor = new CalendarProviderAccessor(this.cordova); @@ -448,8 +455,10 @@ private void deleteEvent(JSONArray args) { return; } - // note that if the dev didn't call requestWritePermission before calling this method and calendarPermissionGranted returns false, - // the app will ask permission and this method needs to be invoked again (done for backward compat). + // note that if the dev didn't call requestWritePermission before calling this + // method and calendarPermissionGranted returns false, + // the app will ask permission and this method needs to be invoked again (done + // for backward compat). if (!calendarPermissionGranted(Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR)) { requestReadWritePermission(PERMISSION_REQCODE_DELETE_EVENT); return; @@ -463,12 +472,12 @@ private void deleteEvent(JSONArray args) { public void run() { boolean deleteResult = getCalendarAccessor().deleteEvent( - null, - jsonFilter.optLong("startTime"), - jsonFilter.optLong("endTime"), - getPossibleNullString("title", jsonFilter), - getPossibleNullString("location", jsonFilter), - getPossibleNullString("notes", jsonFilter)); + null, + jsonFilter.optLong("startTime"), + jsonFilter.optLong("endTime"), + getPossibleNullString("title", jsonFilter), + getPossibleNullString("location", jsonFilter), + getPossibleNullString("notes", jsonFilter)); callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, deleteResult)); } @@ -481,27 +490,32 @@ public void run() { private void deleteEventById(final JSONArray args) { - // note that if the dev didn't call requestWritePermission before calling this method and calendarPermissionGranted returns false, - // the app will ask permission and this method needs to be invoked again (done for backward compat). + // note that if the dev didn't call requestWritePermission before calling this + // method and calendarPermissionGranted returns false, + // the app will ask permission and this method needs to be invoked again (done + // for backward compat). if (!calendarPermissionGranted(Manifest.permission.WRITE_CALENDAR)) { requestWritePermission(PERMISSION_REQCODE_DELETE_EVENT_BY_ID); return; } - cordova.getThreadPool().execute(new Runnable() { @Override public void run() { - try { - final JSONObject opts = args.optJSONObject(0); - final long id = opts != null ? opts.optLong("id", -1) : -1; - final long fromTime = opts != null ? opts.optLong("fromTime", -1) : -1; + cordova.getThreadPool().execute(new Runnable() { + @Override + public void run() { + try { + final JSONObject opts = args.optJSONObject(0); + final long id = opts != null ? opts.optLong("id", -1) : -1; + final long fromTime = opts != null ? opts.optLong("fromTime", -1) : -1; - boolean deleteResult = getCalendarAccessor().deleteEventById(null, id, fromTime); + boolean deleteResult = getCalendarAccessor().deleteEventById(null, id, fromTime); - callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, deleteResult)); - } catch (Exception e) { - System.err.println("Exception: " + e.getMessage()); - callback.error(e.getMessage()); + callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, deleteResult)); + } catch (Exception e) { + System.err.println("Exception: " + e.getMessage()); + callback.error(e.getMessage()); + } } - }}); + }); } private void findEvents(JSONArray args) { @@ -510,8 +524,10 @@ private void findEvents(JSONArray args) { return; } - // note that if the dev didn't call requestReadPermission before calling this method and calendarPermissionGranted returns false, - // the app will ask permission and this method needs to be invoked again (done for backward compat). + // note that if the dev didn't call requestReadPermission before calling this + // method and calendarPermissionGranted returns false, + // the app will ask permission and this method needs to be invoked again (done + // for backward compat). if (!calendarPermissionGranted(Manifest.permission.READ_CALENDAR)) { requestReadPermission(PERMISSION_REQCODE_FIND_EVENTS); return; @@ -525,14 +541,13 @@ private void findEvents(JSONArray args) { @Override public void run() { JSONArray jsonEvents = getCalendarAccessor().findEvents( - getPossibleNullString("id", argOptionsObject), - getPossibleNullString("title", jsonFilter), - getPossibleNullString("location", jsonFilter), - getPossibleNullString("notes", jsonFilter), - jsonFilter.optLong("startTime"), - jsonFilter.optLong("endTime"), - getPossibleNullString("calendarId", argOptionsObject)) - ; + getPossibleNullString("id", argOptionsObject), + getPossibleNullString("title", jsonFilter), + getPossibleNullString("location", jsonFilter), + getPossibleNullString("notes", jsonFilter), + jsonFilter.optLong("startTime"), + jsonFilter.optLong("endTime"), + getPossibleNullString("calendarId", argOptionsObject)); callback.sendPluginResult(new PluginResult(PluginResult.Status.OK, jsonEvents)); } @@ -544,8 +559,10 @@ public void run() { } private void createEvent(JSONArray args) { - // note that if the dev didn't call requestWritePermission before calling this method and calendarPermissionGranted returns false, - // the app will ask permission and this method needs to be invoked again (done for backward compat). + // note that if the dev didn't call requestWritePermission before calling this + // method and calendarPermissionGranted returns false, + // the app will ask permission and this method needs to be invoked again (done + // for backward compat). if (!calendarPermissionGranted(Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR)) { requestReadWritePermission(PERMISSION_REQCODE_CREATE_EVENT); return; @@ -560,24 +577,24 @@ private void createEvent(JSONArray args) { public void run() { try { final String createdEventID = getCalendarAccessor().createEvent( - null, - getPossibleNullString("title", argObject), - argObject.getLong("startTime"), - argObject.getLong("endTime"), - getPossibleNullString("notes", argObject), - getPossibleNullString("location", argObject), - argOptionsObject.optLong("firstReminderMinutes", -1), - argOptionsObject.optLong("secondReminderMinutes", -1), - getPossibleNullString("recurrence", argOptionsObject), - argOptionsObject.optInt("recurrenceInterval", -1), - getPossibleNullString("recurrenceWeekstart", argOptionsObject), - getPossibleNullString("recurrenceByDay", argOptionsObject), - getPossibleNullString("recurrenceByMonthDay", argOptionsObject), - argOptionsObject.optLong("recurrenceEndTime", -1), - argOptionsObject.optLong("recurrenceCount", -1), - getPossibleNullString("allday", argOptionsObject), - argOptionsObject.optInt("calendarId", 1), - getPossibleNullString("url", argOptionsObject)); + null, + getPossibleNullString("title", argObject), + argObject.getLong("startTime"), + argObject.getLong("endTime"), + getPossibleNullString("notes", argObject), + getPossibleNullString("location", argObject), + argOptionsObject.optLong("firstReminderMinutes", -1), + argOptionsObject.optLong("secondReminderMinutes", -1), + getPossibleNullString("recurrence", argOptionsObject), + argOptionsObject.optInt("recurrenceInterval", -1), + getPossibleNullString("recurrenceWeekstart", argOptionsObject), + getPossibleNullString("recurrenceByDay", argOptionsObject), + getPossibleNullString("recurrenceByMonthDay", argOptionsObject), + argOptionsObject.optLong("recurrenceEndTime", -1), + argOptionsObject.optLong("recurrenceCount", -1), + getPossibleNullString("allday", argOptionsObject), + argOptionsObject.optInt("calendarId", 1), + getPossibleNullString("url", argOptionsObject)); if (createdEventID != null) { callback.success(createdEventID); } else { @@ -599,8 +616,10 @@ private static String getPossibleNullString(String param, JSONObject from) { } private void listEventsInRange(JSONArray args) { - // note that if the dev didn't call requestReadPermission before calling this method and calendarPermissionGranted returns false, - // the app will ask permission and this method needs to be invoked again (done for backward compat). + // note that if the dev didn't call requestReadPermission before calling this + // method and calendarPermissionGranted returns false, + // the app will ask permission and this method needs to be invoked again (done + // for backward compat). if (!calendarPermissionGranted(Manifest.permission.READ_CALENDAR)) { requestReadPermission(PERMISSION_REQCODE_LIST_EVENTS_IN_RANGE); return; @@ -612,9 +631,11 @@ private void listEventsInRange(JSONArray args) { final Uri l_eventUri; if (Build.VERSION.SDK_INT >= 8) { - l_eventUri = Uri.parse("content://com.android.calendar/instances/when/" + String.valueOf(input_start_date) + "/" + String.valueOf(input_end_date)); + l_eventUri = Uri.parse("content://com.android.calendar/instances/when/" + String.valueOf(input_start_date) + "/" + + String.valueOf(input_end_date)); } else { - l_eventUri = Uri.parse("content://calendar/instances/when/" + String.valueOf(input_start_date) + "/" + String.valueOf(input_end_date)); + l_eventUri = Uri.parse("content://calendar/instances/when/" + String.valueOf(input_start_date) + "/" + + String.valueOf(input_end_date)); } cordova.getThreadPool().execute(new Runnable() { @@ -626,53 +647,62 @@ public void run() { long input_start_date = jsonFilter.optLong("startTime"); long input_end_date = jsonFilter.optLong("endTime"); - //prepare start date + // prepare start date java.util.Calendar calendar_start = java.util.Calendar.getInstance(); Date date_start = new Date(input_start_date); calendar_start.setTime(date_start); - //prepare end date + // prepare end date java.util.Calendar calendar_end = java.util.Calendar.getInstance(); Date date_end = new Date(input_end_date); calendar_end.setTime(date_end); - //projection of DB columns - String[] l_projection = new String[]{"calendar_id", "title", "begin", "end", "eventLocation", "allDay", "_id", "rrule", "rdate", "exdate", "event_id"}; + // projection of DB columns + String[] l_projection = new String[] { "calendar_id", "title", "begin", "end", "eventLocation", "allDay", + "_id", "rrule", "rdate", "exdate", "event_id" }; - //actual query + // actual query Cursor cursor = contentResolver.query( - l_eventUri, - l_projection, - "(deleted = 0 AND" + - " (" + - // all day events are stored in UTC, others in the user's timezone - " (eventTimezone = 'UTC' AND begin >=" + (calendar_start.getTimeInMillis() + TimeZone.getDefault().getOffset(calendar_start.getTimeInMillis())) + " AND end <=" + (calendar_end.getTimeInMillis() + TimeZone.getDefault().getOffset(calendar_end.getTimeInMillis())) + ")" + - " OR " + - " (eventTimezone <> 'UTC' AND begin >=" + calendar_start.getTimeInMillis() + " AND end <=" + calendar_end.getTimeInMillis() + ")" + - " )" + - ")", - null, - "begin ASC"); + l_eventUri, + l_projection, + "(deleted = 0 AND" + + " (" + + // all day events are stored in UTC, others in the user's timezone + " (eventTimezone = 'UTC' AND begin >=" + + (calendar_start.getTimeInMillis() + + TimeZone.getDefault().getOffset(calendar_start.getTimeInMillis())) + + " AND end <=" + + (calendar_end.getTimeInMillis() + TimeZone.getDefault().getOffset(calendar_end.getTimeInMillis())) + + ")" + + " OR " + + " (eventTimezone <> 'UTC' AND begin >=" + calendar_start.getTimeInMillis() + " AND end <=" + + calendar_end.getTimeInMillis() + ")" + + " )" + + ")", + null, + "begin ASC"); int i = 0; if (cursor != null) { while (cursor.moveToNext()) { try { result.put( - i++, - new JSONObject() - .put("calendar_id", cursor.getString(cursor.getColumnIndex("calendar_id"))) - .put("id", cursor.getString(cursor.getColumnIndex("_id"))) - .put("event_id", cursor.getString(cursor.getColumnIndex("event_id"))) - .put("rrule", cursor.getString(cursor.getColumnIndex("rrule"))) - .put("rdate", cursor.getString(cursor.getColumnIndex("rdate"))) - .put("exdate", cursor.getString(cursor.getColumnIndex("exdate"))) - .put("title", cursor.getString(cursor.getColumnIndex("title"))) - .put("dtstart", cursor.getLong(cursor.getColumnIndex("begin"))) - .put("dtend", cursor.getLong(cursor.getColumnIndex("end"))) - .put("eventLocation", cursor.getString(cursor.getColumnIndex("eventLocation")) != null ? cursor.getString(cursor.getColumnIndex("eventLocation")) : "") - .put("allDay", cursor.getInt(cursor.getColumnIndex("allDay"))) - ); + i++, + new JSONObject() + .put("calendar_id", cursor.getString(cursor.getColumnIndex("calendar_id"))) + .put("id", cursor.getString(cursor.getColumnIndex("_id"))) + .put("event_id", cursor.getString(cursor.getColumnIndex("event_id"))) + .put("rrule", cursor.getString(cursor.getColumnIndex("rrule"))) + .put("rdate", cursor.getString(cursor.getColumnIndex("rdate"))) + .put("exdate", cursor.getString(cursor.getColumnIndex("exdate"))) + .put("title", cursor.getString(cursor.getColumnIndex("title"))) + .put("dtstart", cursor.getLong(cursor.getColumnIndex("begin"))) + .put("dtend", cursor.getLong(cursor.getColumnIndex("end"))) + .put("eventLocation", + cursor.getString(cursor.getColumnIndex("eventLocation")) != null + ? cursor.getString(cursor.getColumnIndex("eventLocation")) + : "") + .put("allDay", cursor.getInt(cursor.getColumnIndex("allDay")))); } catch (JSONException e) { e.printStackTrace(); } @@ -692,7 +722,8 @@ public void run() { public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == RESULT_CODE_CREATE) { if (resultCode == Activity.RESULT_OK || resultCode == Activity.RESULT_CANCELED) { - // resultCode may be 0 (RESULT_CANCELED) even when it was created, so passing nothing is the clearest option here + // resultCode may be 0 (RESULT_CANCELED) even when it was created, so passing + // nothing is the clearest option here Log.d(LOG_TAG, "onActivityResult resultcode: " + resultCode); callback.success(); } else { diff --git a/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java b/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java index 4db0c055..33a99a6c 100644 --- a/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java +++ b/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java @@ -23,756 +23,773 @@ public abstract class AbstractCalendarAccessor { - public static final String LOG_TAG = "Calendar"; - public static final String CONTENT_PROVIDER = "content://com.android.calendar"; - public static final String CONTENT_PROVIDER_PRE_FROYO = "content://calendar"; - - public static final String CONTENT_PROVIDER_PATH_CALENDARS = "/calendars"; - public static final String CONTENT_PROVIDER_PATH_EVENTS = "/events"; - public static final String CONTENT_PROVIDER_PATH_REMINDERS = "/reminders"; - public static final String CONTENT_PROVIDER_PATH_INSTANCES_WHEN = "/instances/when"; - public static final String CONTENT_PROVIDER_PATH_ATTENDEES = "/attendees"; - - protected static class Event { - String id; - String message; - String location; - String title; - String startDate; - String endDate; - String recurrenceFreq; - String recurrenceInterval; - String recurrenceWeekstart; - String recurrenceByDay; - String recurrenceByMonthDay; - String recurrenceUntil; - String recurrenceCount; - //attribute DOMString status; - // attribute DOMString transparency; - // attribute CalendarRepeatRule recurrence; - // attribute DOMString reminder; - - String eventId; - boolean recurring = false; - boolean allDay; - ArrayList attendees; - - public JSONObject toJSONObject() { - JSONObject obj = new JSONObject(); - try { - obj.put("id", this.eventId); - obj.putOpt("message", this.message); - obj.putOpt("location", this.location); - obj.putOpt("title", this.title); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - sdf.setTimeZone(TimeZone.getDefault()); - if (this.startDate != null) { - obj.put("startDate", sdf.format(new Date(Long.parseLong(this.startDate)))); - } - if (this.endDate != null) { - obj.put("endDate", sdf.format(new Date(Long.parseLong(this.endDate)))); - } - obj.put("allday", this.allDay); - if (this.attendees != null) { - JSONArray arr = new JSONArray(); - for (Attendee attendee : this.attendees) { - arr.put(attendee.toJSONObject()); - } - obj.put("attendees", arr); - } - if (this.recurring) { - JSONObject objRecurrence = new JSONObject(); - - objRecurrence.putOpt("freq", this.recurrenceFreq); - objRecurrence.putOpt("interval", this.recurrenceInterval); - objRecurrence.putOpt("wkst", this.recurrenceWeekstart); - objRecurrence.putOpt("byday", this.recurrenceByDay); - objRecurrence.putOpt("bymonthday", this.recurrenceByMonthDay); - objRecurrence.putOpt("until", this.recurrenceUntil); - objRecurrence.putOpt("count", this.recurrenceCount); - - obj.put("recurrence", objRecurrence); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - return obj; - } + public static final String LOG_TAG = "Calendar"; + public static final String CONTENT_PROVIDER = "content://com.android.calendar"; + public static final String CONTENT_PROVIDER_PRE_FROYO = "content://calendar"; + + public static final String CONTENT_PROVIDER_PATH_CALENDARS = "/calendars"; + public static final String CONTENT_PROVIDER_PATH_EVENTS = "/events"; + public static final String CONTENT_PROVIDER_PATH_REMINDERS = "/reminders"; + public static final String CONTENT_PROVIDER_PATH_INSTANCES_WHEN = "/instances/when"; + public static final String CONTENT_PROVIDER_PATH_ATTENDEES = "/attendees"; + + protected static class Event { + String id; + String message; + String location; + String title; + String startDate; + String endDate; + String recurrenceFreq; + String recurrenceInterval; + String recurrenceWeekstart; + String recurrenceByDay; + String recurrenceByMonthDay; + String recurrenceUntil; + String recurrenceCount; + // attribute DOMString status; + // attribute DOMString transparency; + // attribute CalendarRepeatRule recurrence; + // attribute DOMString reminder; + + String eventId; + boolean recurring = false; + boolean allDay; + ArrayList attendees; + + public JSONObject toJSONObject() { + JSONObject obj = new JSONObject(); + try { + obj.put("id", this.eventId); + obj.putOpt("message", this.message); + obj.putOpt("location", this.location); + obj.putOpt("title", this.title); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getDefault()); + if (this.startDate != null) { + obj.put("startDate", sdf.format(new Date(Long.parseLong(this.startDate)))); + } + if (this.endDate != null) { + obj.put("endDate", sdf.format(new Date(Long.parseLong(this.endDate)))); + } + obj.put("allday", this.allDay); + if (this.attendees != null) { + JSONArray arr = new JSONArray(); + for (Attendee attendee : this.attendees) { + arr.put(attendee.toJSONObject()); + } + obj.put("attendees", arr); + } + if (this.recurring) { + JSONObject objRecurrence = new JSONObject(); + + objRecurrence.putOpt("freq", this.recurrenceFreq); + objRecurrence.putOpt("interval", this.recurrenceInterval); + objRecurrence.putOpt("wkst", this.recurrenceWeekstart); + objRecurrence.putOpt("byday", this.recurrenceByDay); + objRecurrence.putOpt("bymonthday", this.recurrenceByMonthDay); + objRecurrence.putOpt("until", this.recurrenceUntil); + objRecurrence.putOpt("count", this.recurrenceCount); + + obj.put("recurrence", objRecurrence); + } + } catch (JSONException e) { + throw new RuntimeException(e); + } + return obj; } - - protected static class Attendee { - String id; - String name; - String email; - String status; - - public JSONObject toJSONObject() { - JSONObject obj = new JSONObject(); - try { - obj.put("id", this.id); - obj.putOpt("name", this.name); - obj.putOpt("email", this.email); - obj.putOpt("status", this.status); - } catch (JSONException e) { - throw new RuntimeException(e); - } - return obj; - } + } + + protected static class Attendee { + String id; + String name; + String email; + String status; + + public JSONObject toJSONObject() { + JSONObject obj = new JSONObject(); + try { + obj.put("id", this.id); + obj.putOpt("name", this.name); + obj.putOpt("email", this.email); + obj.putOpt("status", this.status); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return obj; } - - protected CordovaInterface cordova; - - private EnumMap calendarKeys; - - public AbstractCalendarAccessor(CordovaInterface cordova) { - this.cordova = cordova; - this.calendarKeys = initContentProviderKeys(); + } + + protected CordovaInterface cordova; + + private EnumMap calendarKeys; + + public AbstractCalendarAccessor(CordovaInterface cordova) { + this.cordova = cordova; + this.calendarKeys = initContentProviderKeys(); + } + + protected enum KeyIndex { + CALENDARS_ID, + IS_PRIMARY, + CALENDARS_NAME, + CALENDARS_VISIBLE, + CALENDARS_DISPLAY_NAME, + EVENTS_ID, + EVENTS_CALENDAR_ID, + EVENTS_DESCRIPTION, + EVENTS_LOCATION, + EVENTS_SUMMARY, + EVENTS_START, + EVENTS_END, + EVENTS_RRULE, + EVENTS_ALL_DAY, + INSTANCES_ID, + INSTANCES_EVENT_ID, + INSTANCES_BEGIN, + INSTANCES_END, + ATTENDEES_ID, + ATTENDEES_EVENT_ID, + ATTENDEES_NAME, + ATTENDEES_EMAIL, + ATTENDEES_STATUS + } + + protected abstract EnumMap initContentProviderKeys(); + + protected String getKey(KeyIndex index) { + return this.calendarKeys.get(index); + } + + protected abstract Cursor queryAttendees(String[] projection, + String selection, String[] selectionArgs, String sortOrder); + + protected abstract Cursor queryCalendars(String[] projection, + String selection, String[] selectionArgs, String sortOrder); + + protected abstract Cursor queryEvents(String[] projection, + String selection, String[] selectionArgs, String sortOrder); + + protected abstract Cursor queryEventInstances(long startFrom, long startTo, + String[] projection, String selection, String[] selectionArgs, + String sortOrder); + + private Event[] fetchEventInstances(String eventId, String title, String location, String notes, long startFrom, + long startTo) { + String[] projection = { + this.getKey(KeyIndex.INSTANCES_ID), + this.getKey(KeyIndex.INSTANCES_EVENT_ID), + this.getKey(KeyIndex.INSTANCES_BEGIN), + this.getKey(KeyIndex.INSTANCES_END) + }; + + String sortOrder = this.getKey(KeyIndex.INSTANCES_BEGIN) + " ASC, " + this.getKey(KeyIndex.INSTANCES_END) + " ASC"; + // Fetch events from instances table in ascending order by time. + + // filter + String selection = ""; + List selectionList = new ArrayList(); + + if (eventId != null) { + selection += CalendarContract.Instances.EVENT_ID + " = ?"; + selectionList.add(eventId); + } else { + if (title != null) { + // selection += Events.TITLE + "=?"; + selection += Events.TITLE + " LIKE ?"; + selectionList.add("%" + title + "%"); + } + if (location != null && !location.equals("")) { + if (!"".equals(selection)) { + selection += " AND "; + } + selection += Events.EVENT_LOCATION + " LIKE ?"; + selectionList.add("%" + location + "%"); + } + if (notes != null && !notes.equals("")) { + if (!"".equals(selection)) { + selection += " AND "; + } + selection += Events.DESCRIPTION + " LIKE ?"; + selectionList.add("%" + notes + "%"); + } } - protected enum KeyIndex { - CALENDARS_ID, - IS_PRIMARY, - CALENDARS_NAME, - CALENDARS_VISIBLE, - CALENDARS_DISPLAY_NAME, - EVENTS_ID, - EVENTS_CALENDAR_ID, - EVENTS_DESCRIPTION, - EVENTS_LOCATION, - EVENTS_SUMMARY, - EVENTS_START, - EVENTS_END, - EVENTS_RRULE, - EVENTS_ALL_DAY, - INSTANCES_ID, - INSTANCES_EVENT_ID, - INSTANCES_BEGIN, - INSTANCES_END, - ATTENDEES_ID, - ATTENDEES_EVENT_ID, - ATTENDEES_NAME, - ATTENDEES_EMAIL, - ATTENDEES_STATUS + String[] selectionArgs = new String[selectionList.size()]; + Cursor cursor = queryEventInstances(startFrom, startTo, projection, selection, selectionList.toArray(selectionArgs), + sortOrder); + if (cursor == null) { + return null; } - - protected abstract EnumMap initContentProviderKeys(); - - protected String getKey(KeyIndex index) { - return this.calendarKeys.get(index); + Event[] instances = null; + if (cursor.moveToFirst()) { + int idCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_ID)); + int eventIdCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_EVENT_ID)); + int beginCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_BEGIN)); + int endCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_END)); + int count = cursor.getCount(); + int i = 0; + instances = new Event[count]; + do { + // Use the startDate/endDate time from the instances table. For recurring + // events the events table contain the startDate/endDate time for the + // origin event (as you would expect). + instances[i] = new Event(); + instances[i].id = cursor.getString(idCol); + instances[i].eventId = cursor.getString(eventIdCol); + instances[i].startDate = cursor.getString(beginCol); + instances[i].endDate = cursor.getString(endCol); + i += 1; + } while (cursor.moveToNext()); } - protected abstract Cursor queryAttendees(String[] projection, - String selection, String[] selectionArgs, String sortOrder); - - protected abstract Cursor queryCalendars(String[] projection, - String selection, String[] selectionArgs, String sortOrder); - - protected abstract Cursor queryEvents(String[] projection, - String selection, String[] selectionArgs, String sortOrder); - - protected abstract Cursor queryEventInstances(long startFrom, long startTo, - String[] projection, String selection, String[] selectionArgs, - String sortOrder); - - private Event[] fetchEventInstances(String eventId, String title, String location, String notes, long startFrom, long startTo) { - String[] projection = { - this.getKey(KeyIndex.INSTANCES_ID), - this.getKey(KeyIndex.INSTANCES_EVENT_ID), - this.getKey(KeyIndex.INSTANCES_BEGIN), - this.getKey(KeyIndex.INSTANCES_END) - }; - - String sortOrder = this.getKey(KeyIndex.INSTANCES_BEGIN) + " ASC, " + this.getKey(KeyIndex.INSTANCES_END) + " ASC"; - // Fetch events from instances table in ascending order by time. - - // filter - String selection = ""; - List selectionList = new ArrayList(); - - if (eventId != null) { - selection += CalendarContract.Instances.EVENT_ID + " = ?"; - selectionList.add(eventId); - } else { - if (title != null) { - //selection += Events.TITLE + "=?"; - selection += Events.TITLE + " LIKE ?"; - selectionList.add("%" + title + "%"); - } - if (location != null && !location.equals("")) { - if (!"".equals(selection)) { - selection += " AND "; - } - selection += Events.EVENT_LOCATION + " LIKE ?"; - selectionList.add("%" + location + "%"); - } - if (notes != null && !notes.equals("")) { - if (!"".equals(selection)) { - selection += " AND "; - } - selection += Events.DESCRIPTION + " LIKE ?"; - selectionList.add("%" + notes + "%"); - } - } - - String[] selectionArgs = new String[selectionList.size()]; - Cursor cursor = queryEventInstances(startFrom, startTo, projection, selection, selectionList.toArray(selectionArgs), sortOrder); - if (cursor == null) { - return null; - } - Event[] instances = null; - if (cursor.moveToFirst()) { - int idCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_ID)); - int eventIdCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_EVENT_ID)); - int beginCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_BEGIN)); - int endCol = cursor.getColumnIndex(this.getKey(KeyIndex.INSTANCES_END)); - int count = cursor.getCount(); - int i = 0; - instances = new Event[count]; - do { - // Use the startDate/endDate time from the instances table. For recurring - // events the events table contain the startDate/endDate time for the - // origin event (as you would expect). - instances[i] = new Event(); - instances[i].id = cursor.getString(idCol); - instances[i].eventId = cursor.getString(eventIdCol); - instances[i].startDate = cursor.getString(beginCol); - instances[i].endDate = cursor.getString(endCol); - i += 1; - } while (cursor.moveToNext()); - } - - // if we don't find the event by id, try again by title etc - inline with iOS logic - if ((instances == null || instances.length == 0) && eventId != null) { - return fetchEventInstances(null, title, location, notes, startFrom, startTo); - } else { - return instances; - } + // if we don't find the event by id, try again by title etc - inline with iOS + // logic + if ((instances == null || instances.length == 0) && eventId != null) { + return fetchEventInstances(null, title, location, notes, startFrom, startTo); + } else { + return instances; } - - private String[] getActiveCalendarIds() { - Cursor cursor = queryCalendars(new String[]{ - this.getKey(KeyIndex.CALENDARS_ID) - }, - this.getKey(KeyIndex.CALENDARS_VISIBLE) + "=1", null, null); - String[] calendarIds = null; - if (cursor.moveToFirst()) { - calendarIds = new String[cursor.getCount()]; - int i = 0; - do { - int col = cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_ID)); - calendarIds[i] = cursor.getString(col); - i += 1; - } while (cursor.moveToNext()); - cursor.close(); - } - return calendarIds; - } - - public final JSONArray getActiveCalendars() throws JSONException { - Cursor cursor = queryCalendars( - new String[]{ - this.getKey(KeyIndex.CALENDARS_ID), - this.getKey(KeyIndex.CALENDARS_NAME), - this.getKey(KeyIndex.CALENDARS_DISPLAY_NAME), - this.getKey(KeyIndex.IS_PRIMARY) - }, - this.getKey(KeyIndex.CALENDARS_VISIBLE) + "=1", null, null - ); - if (cursor == null) { - return null; - } - JSONArray calendarsWrapper = new JSONArray(); - int primaryColumnIndex; - if (cursor.moveToFirst()) { - do { - JSONObject calendar = new JSONObject(); - calendar.put("id", cursor.getString(cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_ID)))); - calendar.put("name", cursor.getString(cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_NAME)))); - calendar.put("displayname", cursor.getString(cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_DISPLAY_NAME)))); - primaryColumnIndex = cursor.getColumnIndex(this.getKey((KeyIndex.IS_PRIMARY))); - if (primaryColumnIndex == -1) { - primaryColumnIndex = cursor.getColumnIndex("COALESCE(isPrimary, ownerAccount = account_name)"); - } - calendar.put("isPrimary", "1".equals(cursor.getString(primaryColumnIndex))); - calendarsWrapper.put(calendar); - } while (cursor.moveToNext()); - cursor.close(); - } - return calendarsWrapper; + } + + private String[] getActiveCalendarIds() { + Cursor cursor = queryCalendars(new String[] { + this.getKey(KeyIndex.CALENDARS_ID) + }, + this.getKey(KeyIndex.CALENDARS_VISIBLE) + "=1", null, null); + String[] calendarIds = null; + if (cursor.moveToFirst()) { + calendarIds = new String[cursor.getCount()]; + int i = 0; + do { + int col = cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_ID)); + calendarIds[i] = cursor.getString(col); + i += 1; + } while (cursor.moveToNext()); + cursor.close(); + } + return calendarIds; + } + + public final JSONArray getActiveCalendars() throws JSONException { + Cursor cursor = queryCalendars( + new String[] { + this.getKey(KeyIndex.CALENDARS_ID), + this.getKey(KeyIndex.CALENDARS_NAME), + this.getKey(KeyIndex.CALENDARS_DISPLAY_NAME), + this.getKey(KeyIndex.IS_PRIMARY) + }, + this.getKey(KeyIndex.CALENDARS_VISIBLE) + "=1", null, null); + if (cursor == null) { + return null; + } + JSONArray calendarsWrapper = new JSONArray(); + int primaryColumnIndex; + if (cursor.moveToFirst()) { + do { + JSONObject calendar = new JSONObject(); + calendar.put("id", cursor.getString(cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_ID)))); + calendar.put("name", cursor.getString(cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_NAME)))); + calendar.put("displayname", + cursor.getString(cursor.getColumnIndex(this.getKey(KeyIndex.CALENDARS_DISPLAY_NAME)))); + primaryColumnIndex = cursor.getColumnIndex(this.getKey((KeyIndex.IS_PRIMARY))); + if (primaryColumnIndex == -1) { + primaryColumnIndex = cursor.getColumnIndex("COALESCE(isPrimary, ownerAccount = account_name)"); + } + calendar.put("isPrimary", "1".equals(cursor.getString(primaryColumnIndex))); + calendarsWrapper.put(calendar); + } while (cursor.moveToNext()); + cursor.close(); + } + return calendarsWrapper; + } + + private Map fetchEventsAsMap(Event[] instances, String calendarId) { + // Only selecting from active calendars, no active calendars = no events. + List activeCalendarIds = Arrays.asList(getActiveCalendarIds()); + if (activeCalendarIds.isEmpty()) { + return null; } - private Map fetchEventsAsMap(Event[] instances, String calendarId) { - // Only selecting from active calendars, no active calendars = no events. - List activeCalendarIds = Arrays.asList(getActiveCalendarIds()); - if (activeCalendarIds.isEmpty()) { - return null; - } - - List calendarsToSearch; - - if(calendarId!=null){ - calendarsToSearch = new ArrayList(); - if(activeCalendarIds.contains(calendarId)){ - calendarsToSearch.add(calendarId); - } - - }else{ - calendarsToSearch = activeCalendarIds; - } - - if(calendarsToSearch.isEmpty()){ - return null; - } + List calendarsToSearch; + if (calendarId != null) { + calendarsToSearch = new ArrayList(); + if (activeCalendarIds.contains(calendarId)) { + calendarsToSearch.add(calendarId); + } - String[] projection = new String[]{ - this.getKey(KeyIndex.EVENTS_ID), - this.getKey(KeyIndex.EVENTS_DESCRIPTION), - this.getKey(KeyIndex.EVENTS_LOCATION), - this.getKey(KeyIndex.EVENTS_SUMMARY), - this.getKey(KeyIndex.EVENTS_START), - this.getKey(KeyIndex.EVENTS_END), - this.getKey(KeyIndex.EVENTS_RRULE), - this.getKey(KeyIndex.EVENTS_ALL_DAY) - }; - // Get all the ids at once from active calendars. - StringBuffer select = new StringBuffer(); - select.append(this.getKey(KeyIndex.EVENTS_ID) + " IN ("); - select.append(instances[0].eventId); - for (int i = 1; i < instances.length; i++) { - select.append(","); - select.append(instances[i].eventId); - } - select.append(") AND " + this.getKey(KeyIndex.EVENTS_CALENDAR_ID) + - " IN ("); - - String prefix =""; - for (String calendarToFilterId:calendarsToSearch) { - select.append(prefix); - prefix = ","; - select.append(calendarToFilterId); - } - - select.append(")"); - Cursor cursor = queryEvents(projection, select.toString(), null, null); - Map eventsMap = new HashMap(); - if (cursor.moveToFirst()) { - int[] cols = new int[projection.length]; - for (int i = 0; i < cols.length; i++) { - cols[i] = cursor.getColumnIndex(projection[i]); - } - - do { - Event event = new Event(); - event.id = cursor.getString(cols[0]); - event.message = cursor.getString(cols[1]); - event.location = cursor.getString(cols[2]); - event.title = cursor.getString(cols[3]); - event.startDate = cursor.getString(cols[4]); - event.endDate = cursor.getString(cols[5]); - - String rrule = cursor.getString(cols[6]); - if (!TextUtils.isEmpty(rrule)) { - event.recurring = true; - String[] rrule_rules = cursor.getString(cols[6]).split(";"); - for (String rule : rrule_rules) { - String rule_type = rule.split("=")[0]; - if (rule_type.equals("FREQ")) { - event.recurrenceFreq = rule.split("=")[1]; - } else if (rule_type.equals("INTERVAL")) { - event.recurrenceInterval = rule.split("=")[1]; - } else if (rule_type.equals("WKST")) { - event.recurrenceWeekstart = rule.split("=")[1]; - } else if (rule_type.equals("BYDAY")) { - event.recurrenceByDay = rule.split("=")[1]; - } else if (rule_type.equals("BYMONTHDAY")) { - event.recurrenceByMonthDay = rule.split("=")[1]; - } else if (rule_type.equals("UNTIL")) { - event.recurrenceUntil = rule.split("=")[1]; - } else if (rule_type.equals("COUNT")) { - event.recurrenceCount = rule.split("=")[1]; - } else { - Log.d(LOG_TAG, "Missing handler for " + rule); - } - } - } else { - event.recurring = false; - } - event.allDay = cursor.getInt(cols[7]) != 0; - eventsMap.put(event.id, event); - } while (cursor.moveToNext()); - cursor.close(); - } - return eventsMap; + } else { + calendarsToSearch = activeCalendarIds; } - private Map> fetchAttendeesForEventsAsMap( - String[] eventIds) { - // At least one id. - if (eventIds.length == 0) { - return null; - } - String[] projection = new String[]{ - this.getKey(KeyIndex.ATTENDEES_EVENT_ID), - this.getKey(KeyIndex.ATTENDEES_ID), - this.getKey(KeyIndex.ATTENDEES_NAME), - this.getKey(KeyIndex.ATTENDEES_EMAIL), - this.getKey(KeyIndex.ATTENDEES_STATUS) - }; - StringBuffer select = new StringBuffer(); - select.append(this.getKey(KeyIndex.ATTENDEES_EVENT_ID) + " IN ("); - select.append(eventIds[0]); - for (int i = 1; i < eventIds.length; i++) { - select.append(","); - select.append(eventIds[i]); - } - select.append(")"); - // Group the events together for easy iteration. - Cursor cursor = queryAttendees(projection, select.toString(), null, - this.getKey(KeyIndex.ATTENDEES_EVENT_ID) + " ASC"); - Map> attendeeMap = - new HashMap>(); - if (cursor.moveToFirst()) { - int[] cols = new int[projection.length]; - for (int i = 0; i < cols.length; i++) { - cols[i] = cursor.getColumnIndex(projection[i]); - } - ArrayList array = null; - String currentEventId = null; - do { - String eventId = cursor.getString(cols[0]); - if (currentEventId == null || !currentEventId.equals(eventId)) { - currentEventId = eventId; - array = new ArrayList(); - attendeeMap.put(currentEventId, array); - } - Attendee attendee = new Attendee(); - attendee.id = cursor.getString(cols[1]); - attendee.name = cursor.getString(cols[2]); - attendee.email = cursor.getString(cols[3]); - attendee.status = cursor.getString(cols[4]); - array.add(attendee); - } while (cursor.moveToNext()); - cursor.close(); - } - return attendeeMap; + if (calendarsToSearch.isEmpty()) { + return null; } - public JSONArray findEvents(String eventId, String title, String location, String notes, long startFrom, long startTo, String calendarId) { - JSONArray result = new JSONArray(); - // Fetch events from the instance table. - Event[] instances = fetchEventInstances(eventId, title, location, notes, startFrom, startTo); - if (instances == null) { - return result; - } - // Fetch events from the events table for more event info. - Map eventMap = fetchEventsAsMap(instances, calendarId); - // Fetch event attendees - Map> attendeeMap = - fetchAttendeesForEventsAsMap(eventMap.keySet().toArray(new String[0])); - // Merge the event info with the instances and turn it into a JSONArray. - /*for (Event event : eventMap.values()) { - result.put(event.toJSONObject()); - }*/ - - for (Event instance : instances) { - Event event = eventMap.get(instance.eventId); - if (event != null) { - instance.message = event.message; - instance.location = event.location; - instance.title = event.title; - if (!event.recurring) { - instance.startDate = event.startDate; - instance.endDate = event.endDate; - } - - instance.recurring = event.recurring; - instance.recurrenceFreq = event.recurrenceFreq; - instance.recurrenceInterval = event.recurrenceInterval; - instance.recurrenceWeekstart = event.recurrenceWeekstart; - instance.recurrenceByDay = event.recurrenceByDay; - instance.recurrenceByMonthDay = event.recurrenceByMonthDay; - instance.recurrenceUntil = event.recurrenceUntil; - instance.recurrenceCount = event.recurrenceCount; - - instance.allDay = event.allDay; - instance.attendees = attendeeMap.get(instance.eventId); - result.put(instance.toJSONObject()); - } - } - - return result; + String[] projection = new String[] { + this.getKey(KeyIndex.EVENTS_ID), + this.getKey(KeyIndex.EVENTS_DESCRIPTION), + this.getKey(KeyIndex.EVENTS_LOCATION), + this.getKey(KeyIndex.EVENTS_SUMMARY), + this.getKey(KeyIndex.EVENTS_START), + this.getKey(KeyIndex.EVENTS_END), + this.getKey(KeyIndex.EVENTS_RRULE), + this.getKey(KeyIndex.EVENTS_ALL_DAY) + }; + // Get all the ids at once from active calendars. + StringBuffer select = new StringBuffer(); + select.append(this.getKey(KeyIndex.EVENTS_ID) + " IN ("); + select.append(instances[0].eventId); + for (int i = 1; i < instances.length; i++) { + select.append(","); + select.append(instances[i].eventId); + } + select.append(") AND " + this.getKey(KeyIndex.EVENTS_CALENDAR_ID) + + " IN ("); + + String prefix = ""; + for (String calendarToFilterId : calendarsToSearch) { + select.append(prefix); + prefix = ","; + select.append(calendarToFilterId); } - public boolean deleteEvent(Uri eventsUri, long startFrom, long startTo, String title, String location, String notes) { - ContentResolver resolver = this.cordova.getActivity().getApplicationContext().getContentResolver(); - Event[] events = fetchEventInstances(null, title, location, notes, startFrom, startTo); - int nrDeletedRecords = 0; - if (events != null) { - for (Event event : events) { - Uri eventUri = ContentUris.withAppendedId(eventsUri, Integer.parseInt(event.eventId)); - nrDeletedRecords = resolver.delete(eventUri, null, null); - } - } - return nrDeletedRecords > 0; - } - - public boolean deleteEventById(Uri eventsUri, long id, long fromTime) { - if (id == -1) - throw new IllegalArgumentException("Event id not specified."); - - // Find event - long evDtStart = -1; - String evRRule = null; - { - Cursor cur = queryEvents(new String[] { Events.DTSTART, Events.RRULE }, - Events._ID + " = ?", - new String[] { Long.toString(id) }, - Events.DTSTART); - if (cur.moveToNext()) { - evDtStart = cur.getLong(0); - evRRule = cur.getString(1); - } - cur.close(); - } - if (evDtStart == -1) - throw new RuntimeException("Could not find event."); - - // If targeted, delete initial event - if (fromTime == -1 || evDtStart >= fromTime) { - ContentResolver resolver = this.cordova.getActivity().getContentResolver(); - int deleted = this.cordova.getActivity().getContentResolver() - .delete(ContentUris.withAppendedId(eventsUri, id), null, null); - return deleted > 0; - } - - // Find target instance - long targDtStart = -1; - { - // Scans just over a year. - // Not using a wider range because it can corrupt the Calendar Storage state! https://issuetracker.google.com/issues/36980229 - Cursor cur = queryEventInstances(fromTime, - fromTime + 1000L * 60L * 60L * 24L * 367L, - new String[] { Instances.DTSTART }, - Instances.EVENT_ID + " = ?", - new String[] { Long.toString(id) }, - Instances.DTSTART); - if (cur.moveToNext()) { - targDtStart = cur.getLong(0); - } - cur.close(); - } - if (targDtStart == -1) { - // Nothing to delete - return false; - } - - // Set UNTIL - if (evRRule == null) - evRRule = ""; - - // Remove any existing COUNT or UNTIL - List recurRuleParts = new ArrayList(Arrays.asList(evRRule.split(";"))); - Iterator iter = recurRuleParts.iterator(); - while (iter.hasNext()) { - String rulePart = iter.next(); - if (rulePart.startsWith("COUNT=") || rulePart.startsWith("UNTIL=")) { - iter.remove(); - } - } - - evRRule = TextUtils.join(";", recurRuleParts) + ";UNTIL=" + nl.xservices.plugins.Calendar.formatICalDateTime(new Date(fromTime - 1000)); - - // Update event - ContentValues values = new ContentValues(); - values.put(Events.RRULE, evRRule); - int updated = this.cordova.getActivity().getContentResolver() - .update(ContentUris.withAppendedId(eventsUri, id), values, null, null); - - return updated > 0; - } - - public String createEvent(Uri eventsUri, String title, long startTime, long endTime, String description, - String location, Long firstReminderMinutes, Long secondReminderMinutes, - String recurrence, int recurrenceInterval, String recurrenceWeekstart, - String recurrenceByDay, String recurrenceByMonthDay, Long recurrenceEndTime, Long recurrenceCount, - String allday, - Integer calendarId, String url) { - ContentResolver cr = this.cordova.getActivity().getContentResolver(); - ContentValues values = new ContentValues(); - final boolean allDayEvent = "true".equals(allday) && isAllDayEvent(new Date(startTime), new Date(endTime)); - if (allDayEvent) { - //all day events must be in UTC time zone per Android specification, getOffset accounts for daylight savings time - values.put(Events.EVENT_TIMEZONE, "UTC"); - values.put(Events.DTSTART, startTime + TimeZone.getDefault().getOffset(startTime)); - values.put(Events.DTEND, endTime + TimeZone.getDefault().getOffset(endTime)); - } else { - values.put(Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); - values.put(Events.DTSTART, startTime); - values.put(Events.DTEND, endTime); - } - values.put(Events.ALL_DAY, allDayEvent ? 1 : 0); - values.put(Events.TITLE, title); - // there's no separate url field, so adding it to the notes - if (url != null) { - if (description == null) { - description = url; + select.append(")"); + Cursor cursor = queryEvents(projection, select.toString(), null, null); + Map eventsMap = new HashMap(); + if (cursor.moveToFirst()) { + int[] cols = new int[projection.length]; + for (int i = 0; i < cols.length; i++) { + cols[i] = cursor.getColumnIndex(projection[i]); + } + + do { + Event event = new Event(); + event.id = cursor.getString(cols[0]); + event.message = cursor.getString(cols[1]); + event.location = cursor.getString(cols[2]); + event.title = cursor.getString(cols[3]); + event.startDate = cursor.getString(cols[4]); + event.endDate = cursor.getString(cols[5]); + + String rrule = cursor.getString(cols[6]); + if (!TextUtils.isEmpty(rrule)) { + event.recurring = true; + String[] rrule_rules = cursor.getString(cols[6]).split(";"); + for (String rule : rrule_rules) { + String rule_type = rule.split("=")[0]; + if (rule_type.equals("FREQ")) { + event.recurrenceFreq = rule.split("=")[1]; + } else if (rule_type.equals("INTERVAL")) { + event.recurrenceInterval = rule.split("=")[1]; + } else if (rule_type.equals("WKST")) { + event.recurrenceWeekstart = rule.split("=")[1]; + } else if (rule_type.equals("BYDAY")) { + event.recurrenceByDay = rule.split("=")[1]; + } else if (rule_type.equals("BYMONTHDAY")) { + event.recurrenceByMonthDay = rule.split("=")[1]; + } else if (rule_type.equals("UNTIL")) { + event.recurrenceUntil = rule.split("=")[1]; + } else if (rule_type.equals("COUNT")) { + event.recurrenceCount = rule.split("=")[1]; } else { - description += " " + url; + Log.d(LOG_TAG, "Missing handler for " + rule); } + } + } else { + event.recurring = false; } - values.put(Events.DESCRIPTION, description); - values.put(Events.HAS_ALARM, firstReminderMinutes > -1 || secondReminderMinutes > -1 ? 1 : 0); - values.put(Events.CALENDAR_ID, calendarId); - values.put(Events.EVENT_LOCATION, location); - - if (recurrence != null) { - String rrule = "FREQ=" + recurrence.toUpperCase() + - ((recurrenceInterval > -1) ? ";INTERVAL=" + recurrenceInterval : "") + - ((recurrenceWeekstart != null) ? ";WKST=" + recurrenceWeekstart : "") + - ((recurrenceByDay != null) ? ";BYDAY=" + recurrenceByDay : "") + - ((recurrenceByMonthDay != null) ? ";BYMONTHDAY=" + recurrenceByMonthDay : "") + - ((recurrenceEndTime > -1) ? ";UNTIL=" + nl.xservices.plugins.Calendar.formatICalDateTime(new Date(recurrenceEndTime)) : "") + - ((recurrenceCount > -1) ? ";COUNT=" + recurrenceCount : ""); - values.put(Events.RRULE, rrule); - } - - String createdEventID = null; - try { - Uri uri = cr.insert(eventsUri, values); - createdEventID = uri.getLastPathSegment(); - Log.d(LOG_TAG, "Created event with ID " + createdEventID); - - if (firstReminderMinutes > -1) { - ContentValues reminderValues = new ContentValues(); - reminderValues.put("event_id", Long.parseLong(uri.getLastPathSegment())); - reminderValues.put("minutes", firstReminderMinutes); - reminderValues.put("method", 1); - cr.insert(Uri.parse(CONTENT_PROVIDER + CONTENT_PROVIDER_PATH_REMINDERS), reminderValues); - } + event.allDay = cursor.getInt(cols[7]) != 0; + eventsMap.put(event.id, event); + } while (cursor.moveToNext()); + cursor.close(); + } + return eventsMap; + } + + private Map> fetchAttendeesForEventsAsMap( + String[] eventIds) { + // At least one id. + if (eventIds.length == 0) { + return null; + } + String[] projection = new String[] { + this.getKey(KeyIndex.ATTENDEES_EVENT_ID), + this.getKey(KeyIndex.ATTENDEES_ID), + this.getKey(KeyIndex.ATTENDEES_NAME), + this.getKey(KeyIndex.ATTENDEES_EMAIL), + this.getKey(KeyIndex.ATTENDEES_STATUS) + }; + StringBuffer select = new StringBuffer(); + select.append(this.getKey(KeyIndex.ATTENDEES_EVENT_ID) + " IN ("); + select.append(eventIds[0]); + for (int i = 1; i < eventIds.length; i++) { + select.append(","); + select.append(eventIds[i]); + } + select.append(")"); + // Group the events together for easy iteration. + Cursor cursor = queryAttendees(projection, select.toString(), null, + this.getKey(KeyIndex.ATTENDEES_EVENT_ID) + " ASC"); + Map> attendeeMap = new HashMap>(); + if (cursor.moveToFirst()) { + int[] cols = new int[projection.length]; + for (int i = 0; i < cols.length; i++) { + cols[i] = cursor.getColumnIndex(projection[i]); + } + ArrayList array = null; + String currentEventId = null; + do { + String eventId = cursor.getString(cols[0]); + if (currentEventId == null || !currentEventId.equals(eventId)) { + currentEventId = eventId; + array = new ArrayList(); + attendeeMap.put(currentEventId, array); + } + Attendee attendee = new Attendee(); + attendee.id = cursor.getString(cols[1]); + attendee.name = cursor.getString(cols[2]); + attendee.email = cursor.getString(cols[3]); + attendee.status = cursor.getString(cols[4]); + array.add(attendee); + } while (cursor.moveToNext()); + cursor.close(); + } + return attendeeMap; + } + + public JSONArray findEvents(String eventId, String title, String location, String notes, long startFrom, long startTo, + String calendarId) { + JSONArray result = new JSONArray(); + // Fetch events from the instance table. + Event[] instances = fetchEventInstances(eventId, title, location, notes, startFrom, startTo); + if (instances == null) { + return result; + } + // Fetch events from the events table for more event info. + Map eventMap = fetchEventsAsMap(instances, calendarId); + // Fetch event attendees + Map> attendeeMap = fetchAttendeesForEventsAsMap( + eventMap.keySet().toArray(new String[0])); + // Merge the event info with the instances and turn it into a JSONArray. + /* + * for (Event event : eventMap.values()) { + * result.put(event.toJSONObject()); + * } + */ + + for (Event instance : instances) { + Event event = eventMap.get(instance.eventId); + if (event != null) { + instance.message = event.message; + instance.location = event.location; + instance.title = event.title; + if (!event.recurring) { + instance.startDate = event.startDate; + instance.endDate = event.endDate; + } + + instance.recurring = event.recurring; + instance.recurrenceFreq = event.recurrenceFreq; + instance.recurrenceInterval = event.recurrenceInterval; + instance.recurrenceWeekstart = event.recurrenceWeekstart; + instance.recurrenceByDay = event.recurrenceByDay; + instance.recurrenceByMonthDay = event.recurrenceByMonthDay; + instance.recurrenceUntil = event.recurrenceUntil; + instance.recurrenceCount = event.recurrenceCount; + + instance.allDay = event.allDay; + instance.attendees = attendeeMap.get(instance.eventId); + result.put(instance.toJSONObject()); + } + } - if (secondReminderMinutes > -1) { - ContentValues reminderValues = new ContentValues(); - reminderValues.put("event_id", Long.parseLong(uri.getLastPathSegment())); - reminderValues.put("minutes", secondReminderMinutes); - reminderValues.put("method", 1); - cr.insert(Uri.parse(CONTENT_PROVIDER + CONTENT_PROVIDER_PATH_REMINDERS), reminderValues); - } - } catch (Exception e) { - Log.e(LOG_TAG, "Creating reminders failed, ignoring since the event was created.", e); - } - return createdEventID; - } - - @SuppressWarnings("MissingPermission") // already requested in calling method - public String createCalendar(String calendarName, String calendarColor) { - try { - // don't create if it already exists - Uri evuri = CalendarContract.Calendars.CONTENT_URI; - final ContentResolver contentResolver = cordova.getActivity().getContentResolver(); - Cursor result = contentResolver.query(evuri, new String[]{CalendarContract.Calendars._ID, CalendarContract.Calendars.NAME, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}, null, null, null); - if (result != null) { - while (result.moveToNext()) { - if ((result.getString(1) != null && result.getString(1).equals(calendarName)) || (result.getString(2) != null && result.getString(2).equals(calendarName))) { - result.close(); - return null; - } - } - result.close(); - } + return result; + } + + public boolean deleteEvent(Uri eventsUri, long startFrom, long startTo, String title, String location, String notes) { + ContentResolver resolver = this.cordova.getActivity().getApplicationContext().getContentResolver(); + Event[] events = fetchEventInstances(null, title, location, notes, startFrom, startTo); + int nrDeletedRecords = 0; + if (events != null) { + for (Event event : events) { + Uri eventUri = ContentUris.withAppendedId(eventsUri, Integer.parseInt(event.eventId)); + nrDeletedRecords = resolver.delete(eventUri, null, null); + } + } + return nrDeletedRecords > 0; + } + + public boolean deleteEventById(Uri eventsUri, long id, long fromTime) { + if (id == -1) + throw new IllegalArgumentException("Event id not specified."); + + // Find event + long evDtStart = -1; + String evRRule = null; + { + Cursor cur = queryEvents(new String[] { Events.DTSTART, Events.RRULE }, + Events._ID + " = ?", + new String[] { Long.toString(id) }, + Events.DTSTART); + if (cur.moveToNext()) { + evDtStart = cur.getLong(0); + evRRule = cur.getString(1); + } + cur.close(); + } + if (evDtStart == -1) + throw new RuntimeException("Could not find event."); + + // If targeted, delete initial event + if (fromTime == -1 || evDtStart >= fromTime) { + ContentResolver resolver = this.cordova.getActivity().getContentResolver(); + int deleted = this.cordova.getActivity().getContentResolver() + .delete(ContentUris.withAppendedId(eventsUri, id), null, null); + return deleted > 0; + } - // doesn't exist yet, so create - Uri calUri = CalendarContract.Calendars.CONTENT_URI; - ContentValues cv = new ContentValues(); - cv.put(CalendarContract.Calendars.ACCOUNT_NAME, "AccountName"); - cv.put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL); - cv.put(CalendarContract.Calendars.NAME, calendarName); - cv.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, calendarName); - if (calendarColor != null) { - cv.put(CalendarContract.Calendars.CALENDAR_COLOR, Color.parseColor(calendarColor)); - } - cv.put(CalendarContract.Calendars.VISIBLE, 1); - cv.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER); - cv.put(CalendarContract.Calendars.OWNER_ACCOUNT, "AccountName" ); - cv.put(CalendarContract.Calendars.SYNC_EVENTS, 0); - - calUri = calUri.buildUpon() - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") - .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, "AccountName") - .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) - .build(); - - Uri created = contentResolver.insert(calUri, cv); - if (created != null) { - return created.getLastPathSegment(); - } - } catch (Exception e) { - Log.e(LOG_TAG, "Creating calendar failed.", e); - } - return null; - } - - ; - - @SuppressWarnings("MissingPermission") // already requested in calling method - public void deleteCalendar(String calendarName) { - try { - Uri evuri = CalendarContract.Calendars.CONTENT_URI; - final ContentResolver contentResolver = cordova.getActivity().getContentResolver(); - Cursor result = contentResolver.query(evuri, new String[]{CalendarContract.Calendars._ID, CalendarContract.Calendars.NAME, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}, null, null, null); - if (result != null) { - while (result.moveToNext()) { - if (result.getString(1) != null && result.getString(1).equals(calendarName) || result.getString(2) != null && result.getString(2).equals(calendarName)) { - long calid = result.getLong(0); - Uri deleteUri = ContentUris.withAppendedId(evuri, calid); - contentResolver.delete(deleteUri, null, null); - } - } - result.close(); - } + // Find target instance + long targDtStart = -1; + { + // Scans just over a year. + // Not using a wider range because it can corrupt the Calendar Storage state! + // https://issuetracker.google.com/issues/36980229 + Cursor cur = queryEventInstances(fromTime, + fromTime + 1000L * 60L * 60L * 24L * 367L, + new String[] { Instances.DTSTART }, + Instances.EVENT_ID + " = ?", + new String[] { Long.toString(id) }, + Instances.DTSTART); + if (cur.moveToNext()) { + targDtStart = cur.getLong(0); + } + cur.close(); + } + if (targDtStart == -1) { + // Nothing to delete + return false; + } - // also delete previously crashing calendars, see https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin/issues/241 - deleteCrashingCalendars(contentResolver); - } catch (Throwable t) { - System.err.println(t.getMessage()); - t.printStackTrace(); - } + // Set UNTIL + if (evRRule == null) + evRRule = ""; + + // Remove any existing COUNT or UNTIL + List recurRuleParts = new ArrayList(Arrays.asList(evRRule.split(";"))); + Iterator iter = recurRuleParts.iterator(); + while (iter.hasNext()) { + String rulePart = iter.next(); + if (rulePart.startsWith("COUNT=") || rulePart.startsWith("UNTIL=")) { + iter.remove(); + } } - @SuppressWarnings("MissingPermission") // already requested in calling method - private void deleteCrashingCalendars(ContentResolver contentResolver) { - // first find any faulty Calendars - final String fixingAccountName = "FixingAccountName"; - String selection = CalendarContract.Calendars.ACCOUNT_NAME + " IS NULL"; - Uri uri = CalendarContract.Calendars.CONTENT_URI; - uri = uri.buildUpon() - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") - .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, fixingAccountName) - .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) - .build(); - ContentValues values = new ContentValues(); - values.put(CalendarContract.Calendars.ACCOUNT_NAME, fixingAccountName); - values.put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL); - int count = contentResolver.update(uri, values, selection, null); - - // now delete any faulty Calendars - if (count > 0) { - Uri evuri = CalendarContract.Calendars.CONTENT_URI; - Cursor result = contentResolver.query(evuri, new String[]{CalendarContract.Calendars._ID, CalendarContract.Calendars.ACCOUNT_NAME}, null, null, null); - if (result != null) { - while (result.moveToNext()) { - if (result.getString(1) != null && result.getString(1).equals(fixingAccountName)) { - long calid = result.getLong(0); - Uri deleteUri = ContentUris.withAppendedId(evuri, calid); - contentResolver.delete(deleteUri, null, null); - } - } - result.close(); - } - } + evRRule = TextUtils.join(";", recurRuleParts) + ";UNTIL=" + + nl.xservices.plugins.Calendar.formatICalDateTime(new Date(fromTime - 1000)); + + // Update event + ContentValues values = new ContentValues(); + values.put(Events.RRULE, evRRule); + int updated = this.cordova.getActivity().getContentResolver() + .update(ContentUris.withAppendedId(eventsUri, id), values, null, null); + + return updated > 0; + } + + public String createEvent(Uri eventsUri, String title, long startTime, long endTime, String description, + String location, Long firstReminderMinutes, Long secondReminderMinutes, + String recurrence, int recurrenceInterval, String recurrenceWeekstart, + String recurrenceByDay, String recurrenceByMonthDay, Long recurrenceEndTime, Long recurrenceCount, + String allday, + Integer calendarId, String url) { + ContentResolver cr = this.cordova.getActivity().getContentResolver(); + ContentValues values = new ContentValues(); + final boolean allDayEvent = "true".equals(allday) && isAllDayEvent(new Date(startTime), new Date(endTime)); + if (allDayEvent) { + // all day events must be in UTC time zone per Android specification, getOffset + // accounts for daylight savings time + values.put(Events.EVENT_TIMEZONE, "UTC"); + values.put(Events.DTSTART, startTime + TimeZone.getDefault().getOffset(startTime)); + values.put(Events.DTEND, endTime + TimeZone.getDefault().getOffset(endTime)); + } else { + values.put(Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); + values.put(Events.DTSTART, startTime); + values.put(Events.DTEND, endTime); + } + values.put(Events.ALL_DAY, allDayEvent ? 1 : 0); + values.put(Events.TITLE, title); + // there's no separate url field, so adding it to the notes + if (url != null) { + if (description == null) { + description = url; + } else { + description += " " + url; + } + } + values.put(Events.DESCRIPTION, description); + values.put(Events.HAS_ALARM, firstReminderMinutes > -1 || secondReminderMinutes > -1 ? 1 : 0); + values.put(Events.CALENDAR_ID, calendarId); + values.put(Events.EVENT_LOCATION, location); + + if (recurrence != null) { + String rrule = "FREQ=" + recurrence.toUpperCase() + + ((recurrenceInterval > -1) ? ";INTERVAL=" + recurrenceInterval : "") + + ((recurrenceWeekstart != null) ? ";WKST=" + recurrenceWeekstart : "") + + ((recurrenceByDay != null) ? ";BYDAY=" + recurrenceByDay : "") + + ((recurrenceByMonthDay != null) ? ";BYMONTHDAY=" + recurrenceByMonthDay : "") + + ((recurrenceEndTime > -1) + ? ";UNTIL=" + nl.xservices.plugins.Calendar.formatICalDateTime(new Date(recurrenceEndTime)) + : "") + + + ((recurrenceCount > -1) ? ";COUNT=" + recurrenceCount : ""); + values.put(Events.RRULE, rrule); } - public static boolean isAllDayEvent(final Date startDate, final Date endDate) { - return ((endDate.getTime() - startDate.getTime()) % (24 * 60 * 60 * 1000) == 0); + String createdEventID = null; + try { + Uri uri = cr.insert(eventsUri, values); + createdEventID = uri.getLastPathSegment(); + Log.d(LOG_TAG, "Created event with ID " + createdEventID); + + if (firstReminderMinutes > -1) { + ContentValues reminderValues = new ContentValues(); + reminderValues.put("event_id", Long.parseLong(uri.getLastPathSegment())); + reminderValues.put("minutes", firstReminderMinutes); + reminderValues.put("method", 1); + cr.insert(Uri.parse(CONTENT_PROVIDER + CONTENT_PROVIDER_PATH_REMINDERS), reminderValues); + } + + if (secondReminderMinutes > -1) { + ContentValues reminderValues = new ContentValues(); + reminderValues.put("event_id", Long.parseLong(uri.getLastPathSegment())); + reminderValues.put("minutes", secondReminderMinutes); + reminderValues.put("method", 1); + cr.insert(Uri.parse(CONTENT_PROVIDER + CONTENT_PROVIDER_PATH_REMINDERS), reminderValues); + } + } catch (Exception e) { + Log.e(LOG_TAG, "Creating reminders failed, ignoring since the event was created.", e); + } + return createdEventID; + } + + @SuppressWarnings("MissingPermission") // already requested in calling method + public String createCalendar(String accountName, String calendarName, String calendarColor) { + try { + accountName = (null != accountName && accountName.trim().length() > 0 ? accountName : "AccountName"); + // don't create if it already exists + Uri evuri = CalendarContract.Calendars.CONTENT_URI; + final ContentResolver contentResolver = cordova.getActivity().getContentResolver(); + Cursor result = contentResolver.query(evuri, new String[] { CalendarContract.Calendars._ID, + CalendarContract.Calendars.NAME, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME }, null, null, null); + if (result != null) { + while (result.moveToNext()) { + if ((result.getString(1) != null && result.getString(1).equals(calendarName)) + || (result.getString(2) != null && result.getString(2).equals(calendarName))) { + result.close(); + return null; + } + } + result.close(); + } + + // doesn't exist yet, so create + Uri calUri = CalendarContract.Calendars.CONTENT_URI; + ContentValues cv = new ContentValues(); + cv.put(CalendarContract.Calendars.ACCOUNT_NAME, accountName); + cv.put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL); + cv.put(CalendarContract.Calendars.NAME, calendarName); + cv.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, calendarName); + if (calendarColor != null) { + cv.put(CalendarContract.Calendars.CALENDAR_COLOR, Color.parseColor(calendarColor)); + } + cv.put(CalendarContract.Calendars.VISIBLE, 1); + cv.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER); + cv.put(CalendarContract.Calendars.OWNER_ACCOUNT, accountName); + cv.put(CalendarContract.Calendars.SYNC_EVENTS, 0); + + calUri = calUri.buildUpon() + .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") + .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, accountName) + .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) + .build(); + + Uri created = contentResolver.insert(calUri, cv); + if (created != null) { + return created.getLastPathSegment(); + } + } catch (Exception e) { + Log.e(LOG_TAG, "Creating calendar failed.", e); } + return null; + } + + ; + + @SuppressWarnings("MissingPermission") // already requested in calling method + public void deleteCalendar(String calendarName) { + try { + Uri evuri = CalendarContract.Calendars.CONTENT_URI; + final ContentResolver contentResolver = cordova.getActivity().getContentResolver(); + Cursor result = contentResolver.query(evuri, new String[] { CalendarContract.Calendars._ID, + CalendarContract.Calendars.NAME, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME }, null, null, null); + if (result != null) { + while (result.moveToNext()) { + if (result.getString(1) != null && result.getString(1).equals(calendarName) + || result.getString(2) != null && result.getString(2).equals(calendarName)) { + long calid = result.getLong(0); + Uri deleteUri = ContentUris.withAppendedId(evuri, calid); + contentResolver.delete(deleteUri, null, null); + } + } + result.close(); + } + + // also delete previously crashing calendars, see + // https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin/issues/241 + deleteCrashingCalendars(contentResolver); + } catch (Throwable t) { + System.err.println(t.getMessage()); + t.printStackTrace(); + } + } + + @SuppressWarnings("MissingPermission") // already requested in calling method + private void deleteCrashingCalendars(ContentResolver contentResolver) { + // first find any faulty Calendars + final String fixingAccountName = "FixingAccountName"; + String selection = CalendarContract.Calendars.ACCOUNT_NAME + " IS NULL"; + Uri uri = CalendarContract.Calendars.CONTENT_URI; + uri = uri.buildUpon() + .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") + .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, fixingAccountName) + .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) + .build(); + ContentValues values = new ContentValues(); + values.put(CalendarContract.Calendars.ACCOUNT_NAME, fixingAccountName); + values.put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL); + int count = contentResolver.update(uri, values, selection, null); + + // now delete any faulty Calendars + if (count > 0) { + Uri evuri = CalendarContract.Calendars.CONTENT_URI; + Cursor result = contentResolver.query(evuri, + new String[] { CalendarContract.Calendars._ID, CalendarContract.Calendars.ACCOUNT_NAME }, null, null, null); + if (result != null) { + while (result.moveToNext()) { + if (result.getString(1) != null && result.getString(1).equals(fixingAccountName)) { + long calid = result.getLong(0); + Uri deleteUri = ContentUris.withAppendedId(evuri, calid); + contentResolver.delete(deleteUri, null, null); + } + } + result.close(); + } + } + } + + public static boolean isAllDayEvent(final Date startDate, final Date endDate) { + return ((endDate.getTime() - startDate.getTime()) % (24 * 60 * 60 * 1000) == 0); + } } From b648e7bb05d7381b17ee92f2a959eb038daf8294 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Mon, 7 Mar 2022 10:49:42 +0100 Subject: [PATCH 2/5] added posibility to set accountname for android --- www/Calendar.js | 581 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 459 insertions(+), 122 deletions(-) diff --git a/www/Calendar.js b/www/Calendar.js index 4b0b5d62..94ddb282 100644 --- a/www/Calendar.js +++ b/www/Calendar.js @@ -1,43 +1,101 @@ -"use strict"; -function Calendar() { -} +'use strict'; +function Calendar() {} Calendar.prototype.getCreateCalendarOptions = function () { return { + accountName: 'AccountName', calendarName: null, - calendarColor: null // optional, the OS will choose one if left empty, example: pass "#FF0000" for red + calendarColor: null, // optional, the OS will choose one if left empty, example: pass "#FF0000" for red }; }; -Calendar.prototype.hasReadPermission = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "hasReadPermission", []); +Calendar.prototype.hasReadPermission = function ( + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'hasReadPermission', + [] + ); }; -Calendar.prototype.requestReadPermission = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "requestReadPermission", []); +Calendar.prototype.requestReadPermission = function ( + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'requestReadPermission', + [] + ); }; -Calendar.prototype.hasWritePermission = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "hasWritePermission", []); +Calendar.prototype.hasWritePermission = function ( + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'hasWritePermission', + [] + ); }; -Calendar.prototype.requestWritePermission = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "requestWritePermission", []); +Calendar.prototype.requestWritePermission = function ( + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'requestWritePermission', + [] + ); }; -Calendar.prototype.hasReadWritePermission = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "hasReadWritePermission", []); +Calendar.prototype.hasReadWritePermission = function ( + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'hasReadWritePermission', + [] + ); }; -Calendar.prototype.requestReadWritePermission = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "requestReadWritePermission", []); +Calendar.prototype.requestReadWritePermission = function ( + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'requestReadWritePermission', + [] + ); }; -Calendar.prototype.createCalendar = function (calendarNameOrOptionsObject, successCallback, errorCallback) { +Calendar.prototype.createCalendar = function ( + calendarNameOrOptionsObject, + successCallback, + errorCallback +) { var options; - if (typeof calendarNameOrOptionsObject == "string") { + if (typeof calendarNameOrOptionsObject == 'string') { options = { - "calendarName": calendarNameOrOptionsObject + calendarName: calendarNameOrOptionsObject, }; } else { options = calendarNameOrOptionsObject; @@ -49,23 +107,37 @@ Calendar.prototype.createCalendar = function (calendarNameOrOptionsObject, succe mergedOptions[val] = options[val]; } } - cordova.exec(successCallback, errorCallback, "Calendar", "createCalendar", [mergedOptions]); + cordova.exec(successCallback, errorCallback, 'Calendar', 'createCalendar', [ + mergedOptions, + ]); }; -Calendar.prototype.deleteCalendar = function (calendarName, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "deleteCalendar", [{ - "calendarName": calendarName - }]); +Calendar.prototype.deleteCalendar = function ( + calendarName, + successCallback, + errorCallback +) { + cordova.exec(successCallback, errorCallback, 'Calendar', 'deleteCalendar', [ + { + calendarName: calendarName, + }, + ]); }; -Calendar.prototype.openCalendar = function (date, successCallback, errorCallback) { +Calendar.prototype.openCalendar = function ( + date, + successCallback, + errorCallback +) { // default: today if (!(date instanceof Date)) { date = new Date(); } - cordova.exec(successCallback, errorCallback, "Calendar", "openCalendar", [{ - "date": date.getTime() - }]); + cordova.exec(successCallback, errorCallback, 'Calendar', 'openCalendar', [ + { + date: date.getTime(), + }, + ]); }; Calendar.prototype.getCalendarOptions = function () { @@ -74,14 +146,14 @@ Calendar.prototype.getCalendarOptions = function () { secondReminderMinutes: null, recurrence: null, // options are: 'daily', 'weekly', 'monthly', 'yearly' recurrenceInterval: 1, // only used when recurrence is set - recurrenceWeekstart: "MO", + recurrenceWeekstart: 'MO', recurrenceByDay: null, recurrenceByMonthDay: null, recurrenceEndDate: null, recurrenceCount: null, calendarName: null, calendarId: null, - url: null + url: null, }; }; @@ -91,9 +163,18 @@ Calendar.prototype.getCalendarOptions = function () { * var options = window.plugins.calendar.getCalendarOptions(); * options.firstReminderMinutes = 150; */ -Calendar.prototype.createEventWithOptions = function (title, location, notes, startDate, endDate, options, successCallback, errorCallback) { +Calendar.prototype.createEventWithOptions = function ( + title, + location, + notes, + startDate, + endDate, + options, + successCallback, + errorCallback +) { if (!(startDate instanceof Date && endDate instanceof Date)) { - errorCallback("startDate and endDate must be JavaScript Date Objects"); + errorCallback('startDate and endDate must be JavaScript Date Objects'); return; } @@ -107,32 +188,101 @@ Calendar.prototype.createEventWithOptions = function (title, location, notes, st if (options.recurrenceEndDate != null) { mergedOptions.recurrenceEndTime = options.recurrenceEndDate.getTime(); } - cordova.exec(successCallback, errorCallback, "Calendar", "createEventWithOptions", [{ - "title": title, - "location": location, - "notes": notes, - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null, - "options": mergedOptions - }]); + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'createEventWithOptions', + [ + { + title: title, + location: location, + notes: notes, + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + options: mergedOptions, + }, + ] + ); }; /** * @deprecated use createEventWithOptions instead */ -Calendar.prototype.createEventInNamedCalendar = function (title, location, notes, startDate, endDate, calendarName, successCallback, errorCallback) { - Calendar.prototype.createEventWithOptions(title, location, notes, startDate, endDate, {calendarName:calendarName}, successCallback, errorCallback); +Calendar.prototype.createEventInNamedCalendar = function ( + title, + location, + notes, + startDate, + endDate, + calendarName, + successCallback, + errorCallback +) { + Calendar.prototype.createEventWithOptions( + title, + location, + notes, + startDate, + endDate, + { calendarName: calendarName }, + successCallback, + errorCallback + ); }; -Calendar.prototype.createEvent = function (title, location, notes, startDate, endDate, successCallback, errorCallback) { - Calendar.prototype.createEventWithOptions(title, location, notes, startDate, endDate, {}, successCallback, errorCallback); +Calendar.prototype.createEvent = function ( + title, + location, + notes, + startDate, + endDate, + successCallback, + errorCallback +) { + Calendar.prototype.createEventWithOptions( + title, + location, + notes, + startDate, + endDate, + {}, + successCallback, + errorCallback + ); }; -Calendar.prototype.createEventInteractively = function (title, location, notes, startDate, endDate, successCallback, errorCallback) { - Calendar.prototype.createEventInteractivelyWithOptions(title, location, notes, startDate, endDate, {}, successCallback, errorCallback); +Calendar.prototype.createEventInteractively = function ( + title, + location, + notes, + startDate, + endDate, + successCallback, + errorCallback +) { + Calendar.prototype.createEventInteractivelyWithOptions( + title, + location, + notes, + startDate, + endDate, + {}, + successCallback, + errorCallback + ); }; -Calendar.prototype.createEventInteractivelyWithOptions = function (title, location, notes, startDate, endDate, options, successCallback, errorCallback) { +Calendar.prototype.createEventInteractivelyWithOptions = function ( + title, + location, + notes, + startDate, + endDate, + options, + successCallback, + errorCallback +) { // merge passed options with defaults var mergedOptions = Calendar.prototype.getCalendarOptions(); for (var val in options) { @@ -143,17 +293,34 @@ Calendar.prototype.createEventInteractivelyWithOptions = function (title, locati if (options.recurrenceEndDate != null) { mergedOptions.recurrenceEndTime = options.recurrenceEndDate.getTime(); } - cordova.exec(successCallback, errorCallback, "Calendar", "createEventInteractively", [{ - "title": title, - "location": location, - "notes": notes, - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null, - "options": mergedOptions - }]) + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'createEventInteractively', + [ + { + title: title, + location: location, + notes: notes, + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + options: mergedOptions, + }, + ] + ); }; -Calendar.prototype.findEventWithOptions = function (title, location, notes, startDate, endDate, options, successCallback, errorCallback) { +Calendar.prototype.findEventWithOptions = function ( + title, + location, + notes, + startDate, + endDate, + options, + successCallback, + errorCallback +) { // merge passed options with defaults var mergedOptions = Calendar.prototype.getCalendarOptions(); for (var val in options) { @@ -164,60 +331,148 @@ Calendar.prototype.findEventWithOptions = function (title, location, notes, star if (options.recurrenceEndDate != null) { mergedOptions.recurrenceEndTime = options.recurrenceEndDate.getTime(); } - cordova.exec(successCallback, errorCallback, "Calendar", "findEventWithOptions", [{ - "title": title, - "location": location, - "notes": notes, - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null, - "options": mergedOptions - }]) + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'findEventWithOptions', + [ + { + title: title, + location: location, + notes: notes, + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + options: mergedOptions, + }, + ] + ); }; -Calendar.prototype.findEvent = function (title, location, notes, startDate, endDate, successCallback, errorCallback) { - Calendar.prototype.findEventWithOptions(title, location, notes, startDate, endDate, {}, successCallback, errorCallback); +Calendar.prototype.findEvent = function ( + title, + location, + notes, + startDate, + endDate, + successCallback, + errorCallback +) { + Calendar.prototype.findEventWithOptions( + title, + location, + notes, + startDate, + endDate, + {}, + successCallback, + errorCallback + ); }; -Calendar.prototype.findAllEventsInNamedCalendar = function (calendarName, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "findAllEventsInNamedCalendar", [{ - "calendarName": calendarName - }]); +Calendar.prototype.findAllEventsInNamedCalendar = function ( + calendarName, + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'findAllEventsInNamedCalendar', + [ + { + calendarName: calendarName, + }, + ] + ); }; -Calendar.prototype.deleteEvent = function (title, location, notes, startDate, endDate, successCallback, errorCallback) { +Calendar.prototype.deleteEvent = function ( + title, + location, + notes, + startDate, + endDate, + successCallback, + errorCallback +) { if (!(startDate instanceof Date && endDate instanceof Date)) { - errorCallback("startDate and endDate must be JavaScript Date Objects"); + errorCallback('startDate and endDate must be JavaScript Date Objects'); } - cordova.exec(successCallback, errorCallback, "Calendar", "deleteEvent", [{ - "title": title, - "location": location, - "notes": notes, - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null - }]) + cordova.exec(successCallback, errorCallback, 'Calendar', 'deleteEvent', [ + { + title: title, + location: location, + notes: notes, + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + }, + ]); }; -Calendar.prototype.deleteEventFromNamedCalendar = function (title, location, notes, startDate, endDate, calendarName, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "deleteEventFromNamedCalendar", [{ - "title": title, - "location": location, - "notes": notes, - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null, - "calendarName": calendarName - }]) +Calendar.prototype.deleteEventFromNamedCalendar = function ( + title, + location, + notes, + startDate, + endDate, + calendarName, + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'deleteEventFromNamedCalendar', + [ + { + title: title, + location: location, + notes: notes, + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + calendarName: calendarName, + }, + ] + ); }; -Calendar.prototype.deleteEventById = function (id, fromDate, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "deleteEventById", [{ - "id": id, - "fromTime": fromDate instanceof Date ? fromDate.getTime() : null - }]); +Calendar.prototype.deleteEventById = function ( + id, + fromDate, + successCallback, + errorCallback +) { + cordova.exec(successCallback, errorCallback, 'Calendar', 'deleteEventById', [ + { + id: id, + fromTime: fromDate instanceof Date ? fromDate.getTime() : null, + }, + ]); }; -Calendar.prototype.modifyEventWithOptions = function (title, location, notes, startDate, endDate, newTitle, newLocation, newNotes, newStartDate, newEndDate, options, newOptions, successCallback, errorCallback) { +Calendar.prototype.modifyEventWithOptions = function ( + title, + location, + notes, + startDate, + endDate, + newTitle, + newLocation, + newNotes, + newStartDate, + newEndDate, + options, + newOptions, + successCallback, + errorCallback +) { if (!(newStartDate instanceof Date && newEndDate instanceof Date)) { - errorCallback("newStartDate and newEndDate must be JavaScript Date Objects"); + errorCallback( + 'newStartDate and newEndDate must be JavaScript Date Objects' + ); return; } // merge passed options with defaults @@ -240,41 +495,118 @@ Calendar.prototype.modifyEventWithOptions = function (title, location, notes, st if (newOptions.recurrenceEndDate != null) { newMergedOptions.recurrenceEndTime = newOptions.recurrenceEndDate.getTime(); } - cordova.exec(successCallback, errorCallback, "Calendar", "modifyEventWithOptions", [{ - "title": title, - "location": location, - "notes": notes, - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null, - "newTitle": newTitle, - "newLocation": newLocation, - "newNotes": newNotes, - "newStartTime": newStartDate instanceof Date ? newStartDate.getTime() : null, - "newEndTime": newEndDate instanceof Date ? newEndDate.getTime() : null, - "options": mergedOptions, - "newOptions": newMergedOptions - }]) + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'modifyEventWithOptions', + [ + { + title: title, + location: location, + notes: notes, + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + newTitle: newTitle, + newLocation: newLocation, + newNotes: newNotes, + newStartTime: + newStartDate instanceof Date ? newStartDate.getTime() : null, + newEndTime: newEndDate instanceof Date ? newEndDate.getTime() : null, + options: mergedOptions, + newOptions: newMergedOptions, + }, + ] + ); }; -Calendar.prototype.modifyEvent = function (title, location, notes, startDate, endDate, newTitle, newLocation, newNotes, newStartDate, newEndDate, successCallback, errorCallback) { - Calendar.prototype.modifyEventWithOptions(title, location, notes, startDate, endDate, newTitle, newLocation, newNotes, newStartDate, newEndDate, {}, successCallback, errorCallback); +Calendar.prototype.modifyEvent = function ( + title, + location, + notes, + startDate, + endDate, + newTitle, + newLocation, + newNotes, + newStartDate, + newEndDate, + successCallback, + errorCallback +) { + Calendar.prototype.modifyEventWithOptions( + title, + location, + notes, + startDate, + endDate, + newTitle, + newLocation, + newNotes, + newStartDate, + newEndDate, + {}, + successCallback, + errorCallback + ); }; -Calendar.prototype.modifyEventInNamedCalendar = function (title, location, notes, startDate, endDate, newTitle, newLocation, newNotes, newStartDate, newEndDate, calendarName, successCallback, errorCallback) { +Calendar.prototype.modifyEventInNamedCalendar = function ( + title, + location, + notes, + startDate, + endDate, + newTitle, + newLocation, + newNotes, + newStartDate, + newEndDate, + calendarName, + successCallback, + errorCallback +) { var options = Calendar.prototype.getCalendarOptions(); options.calendarName = calendarName; - Calendar.prototype.modifyEventWithOptions(title, location, notes, startDate, endDate, newTitle, newLocation, newNotes, newStartDate, newEndDate, options, successCallback, errorCallback); + Calendar.prototype.modifyEventWithOptions( + title, + location, + notes, + startDate, + endDate, + newTitle, + newLocation, + newNotes, + newStartDate, + newEndDate, + options, + successCallback, + errorCallback + ); }; -Calendar.prototype.listEventsInRange = function (startDate, endDate, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "listEventsInRange", [{ - "startTime": startDate instanceof Date ? startDate.getTime() : null, - "endTime": endDate instanceof Date ? endDate.getTime() : null - }]) +Calendar.prototype.listEventsInRange = function ( + startDate, + endDate, + successCallback, + errorCallback +) { + cordova.exec( + successCallback, + errorCallback, + 'Calendar', + 'listEventsInRange', + [ + { + startTime: startDate instanceof Date ? startDate.getTime() : null, + endTime: endDate instanceof Date ? endDate.getTime() : null, + }, + ] + ); }; Calendar.prototype.listCalendars = function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Calendar", "listCalendars", []); + cordova.exec(successCallback, errorCallback, 'Calendar', 'listCalendars', []); }; Calendar.prototype.parseEventDate = function (dateStr) { @@ -285,9 +617,14 @@ Calendar.prototype.parseEventDate = function (dateStr) { var spl; // Handle yyyy-MM-dd HH:mm:ss format returned by AbstractCalendarAccessor.java L66 and Calendar.m L378, and yyyyMMddTHHmmss iCalendar local format, and similar - return (spl = /^\s*(\d{4})\D?(\d{2})\D?(\d{2})\D?(\d{2})\D?(\d{2})\D?(\d{2})\s*$/.exec(dateStr)) - && new Date(spl[1], spl[2] - 1, spl[3], spl[4], spl[5], spl[6]) - || new Date(dateStr); + return ( + ((spl = + /^\s*(\d{4})\D?(\d{2})\D?(\d{2})\D?(\d{2})\D?(\d{2})\D?(\d{2})\s*$/.exec( + dateStr + )) && + new Date(spl[1], spl[2] - 1, spl[3], spl[4], spl[5], spl[6])) || + new Date(dateStr) + ); }; Calendar.install = function () { From ef91d311e3d8397837d56c7c1d1efa272d6af83e Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Mon, 2 Oct 2023 09:34:04 +0200 Subject: [PATCH 3/5] (fix) added ios17 permission check --- .gitignore | 5 ++++- src/ios/Calendar.m | 31 ++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index c9f525f0..e16aacd4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ demo/platforms demo/plugins *.ipr -.idea/ \ No newline at end of file +.idea/ +/bin/ +.classpath +.project \ No newline at end of file diff --git a/src/ios/Calendar.m b/src/ios/Calendar.m index eb246455..1bc29767 100644 --- a/src/ios/Calendar.m +++ b/src/ios/Calendar.m @@ -16,17 +16,30 @@ - (void) pluginInitialize { } - (void) initEventStoreWithCalendarCapabilities { - __block BOOL accessGranted = NO; EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init]; - if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) { - dispatch_semaphore_t sema = dispatch_semaphore_create(0); - [eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { - accessGranted = granted; - dispatch_semaphore_signal(sema); + + if (@available(iOS 17.0, *)) { + [eventStoreCandidate requestFullAccessToEventsWithCompletion:^(BOOL granted, NSError *error) { + if (granted) { + self.eventStore = eventStoreCandidate; + NSLog(@"Full access to the event store granted and eventStore initialized."); + } else { + NSLog(@"Access to the event store not granted. Error: %@", error.localizedDescription); + } }]; - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - } else { // we're on iOS 5 or older - accessGranted = YES; + } else { + __block BOOL accessGranted = NO; + + if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + [eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { + accessGranted = granted; + dispatch_semaphore_signal(sema); + }]; + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + } else { // we're on iOS 5 or older + accessGranted = YES; + } } if (accessGranted) { From 750d4726dd7b9160014e1c13ca7a6ef8e1d33598 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 5 Oct 2023 11:08:48 +0200 Subject: [PATCH 4/5] ios17 fix for calendar access --- README.md | 474 +++++++++++++++++++++++++++++---------------- src/ios/Calendar.h | 59 +++--- src/ios/Calendar.m | 104 ++++++++-- 3 files changed, 420 insertions(+), 217 deletions(-) diff --git a/README.md b/README.md index 78bbe5a3..ae255a1c 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,19 @@ [![TotalDownloads][total-downloads-image]][npm-url] [![Twitter Follow][twitter-image]][twitter-url] -[npm-image]:http://img.shields.io/npm/v/cordova-plugin-calendar.svg -[npm-url]:https://npmjs.org/package/cordova-plugin-calendar -[downloads-image]:http://img.shields.io/npm/dm/cordova-plugin-calendar.svg -[total-downloads-image]:http://img.shields.io/npm/dt/cordova-plugin-calendar.svg?label=total%20downloads -[twitter-image]:https://img.shields.io/twitter/follow/eddyverbruggen.svg?style=social&label=Follow%20me -[twitter-url]:https://twitter.com/eddyverbruggen - +[npm-image]: http://img.shields.io/npm/v/cordova-plugin-calendar.svg +[npm-url]: https://npmjs.org/package/cordova-plugin-calendar +[downloads-image]: http://img.shields.io/npm/dm/cordova-plugin-calendar.svg +[total-downloads-image]: http://img.shields.io/npm/dt/cordova-plugin-calendar.svg?label=total%20downloads +[twitter-image]: https://img.shields.io/twitter/follow/eddyverbruggen.svg?style=social&label=Follow%20me +[twitter-url]: https://twitter.com/eddyverbruggen [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=eddyverbruggen%40gmail%2ecom&lc=US&item_name=cordova%2dplugin%2dcalendar¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted) Every now and then kind folks ask me how they can give me all their money. Of course I'm happy to receive any amount but I'm just as happy if you simply 'star' this project. 1. [Description](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#1-description) -2. [Installation](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#2-installation) - 2. [Automatically](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#automatically) - 2. [Manually](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#manually) - 2. [PhoneGap Build](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#phonegap-build) +2. [Installation](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#2-installation) 2. [Automatically](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#automatically) 2. [Manually](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#manually) 2. [PhoneGap Build](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#phonegap-build) 3. [Usage](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#3-usage) 4. [Promises](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#4-promises) 5. [Credits](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#5-credits) @@ -31,37 +27,49 @@ Of course I'm happy to receive any amount but I'm just as happy if you simply 's This plugin allows you to add events to the Calendar of the mobile device. -* Works with PhoneGap >= 3.0. -* For PhoneGap 2.x, see [the pre-3.0 branch](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin/tree/pre-3.0). -* Compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman). -* [Officially supported by PhoneGap Build](https://build.phonegap.com/plugins). +- Works with PhoneGap >= 3.0. +- For PhoneGap 2.x, see [the pre-3.0 branch](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin/tree/pre-3.0). +- Compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman). +- [Officially supported by PhoneGap Build](https://build.phonegap.com/plugins). ### iOS specifics -* Supported methods: `find`, `create`, `modify`, `delete`, .. -* All methods work without showing the native calendar. Your app never loses control. -* Tested on iOS 6+. -* On iOS 10+ you need to provide a reason to the user for Calendar access. This plugin adds an empty `NSCalendarsUsageDescription` key to the /platforms/ios/*-Info.plist file which you can override with your custom string. To do so, pass the following variable when installing the plugin: + +- Supported methods: `find`, `create`, `modify`, `delete`, .. +- All methods work without showing the native calendar. Your app never loses control. +- Tested on iOS 6+. +- On iOS 10+ you need to provide a reason to the user for Calendar access. This plugin adds an empty `NSCalendarsUsageDescription` key to the /platforms/ios/\*-Info.plist file which you can override with your custom string. To do so, pass the following variable when installing the plugin: + +- On iOS 16 and below, all permission requests are identical. They all request full access. +- On iOS 17+, the `requestReadPermission()` and `requestReadWritePermission()` both request full access. `requestWritePermission()` only requests write permission. + - Note: calling requestWritePermission() after calling requestReadWritePermission() may mean you lose permission to read events. You may need to call requestReadWritePermission() again. +- On iOS 17+ you need to add `NSCalendarsFullAccessUsageDescription` and `NSCalendarsWriteOnlyAccessUsageDescription` usage strings to the `.plist` files. +- ``` cordova plugin add cordova-plugin-calendar --variable CALENDAR_USAGE_DESCRIPTION="This app uses your calendar" ``` ### Android specifics -* Supported methods on Android 4: `find`, `create` (silent and interactive), `delete`, .. -* Supported methods on Android 2 and 3: `create` interactive only: the user is presented a prefilled Calendar event. Pressing the hardware back button will give control back to your app. + +- Supported methods on Android 4: `find`, `create` (silent and interactive), `delete`, .. +- Supported methods on Android 2 and 3: `create` interactive only: the user is presented a prefilled Calendar event. Pressing the hardware back button will give control back to your app. ### Windows 10 Mobile -* Supported methods: `createEvent`, `createEventWithOptions`, `createEventInteractively`, `createEventInteractivelyWithOptions` only interactively + +- Supported methods: `createEvent`, `createEventWithOptions`, `createEventInteractively`, `createEventInteractivelyWithOptions` only interactively ## 2. Installation ### Automatically + Latest release on npm: + ``` $ cordova plugin add cordova-plugin-calendar ``` Bleeding edge, from github: + ``` $ cordova plugin add https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin.git ``` @@ -71,6 +79,7 @@ $ cordova plugin add https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin. #### iOS 1\. Add the following xml to your `config.xml`: + ```xml @@ -79,6 +88,7 @@ $ cordova plugin add https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin. ``` 2\. Grab a copy of Calendar.js, add it to your project and reference it in `index.html`: + ```html ``` @@ -92,6 +102,7 @@ Copy `Calendar.h` and `Calendar.m` to `platforms/ios//Plugins` #### Android 1\. Add the following xml to your `config.xml`: + ```xml @@ -100,6 +111,7 @@ Copy `Calendar.h` and `Calendar.m` to `platforms/ios//Plugins` ``` 2\. Grab a copy of Calendar.js, add it to your project and reference it in `index.html`: + ```html ``` @@ -110,6 +122,7 @@ Android: Copy `Calendar.java` to `platforms/android/src/nl/xservices/plugins` (c Then create a package called `accessor` and copy other 3 java Classes into it. 4\. Add these permissions to your AndroidManifest.xml: + ```xml @@ -118,15 +131,16 @@ Then create a package called `accessor` and copy other 3 java Classes into it. Note that if you don't want your app to ask for these permissions, you can leave them out, but you'll only be able to use one function of this plugin: `createEventInteractively`. - ### PhoneGap Build Add the following xml to your `config.xml` to always use the latest npm version of this plugin: + ```xml ``` Also, make sure you're building with Gradle by adding this to your `config.xml` file: + ```xml ``` @@ -135,149 +149,266 @@ Also, make sure you're building with Gradle by adding this to your `config.xml` The table gives an overview of basic operation compatibility: -Operation | Comment | iOS | Android | Windows | ------------------------------------ | ----------- | --- | ------- | ------- | -createCalendar | | yes | yes | | -deleteCalendar | | yes | yes | | -createEvent | silent | yes | yes * | yes ** | -createEventWithOptions | silent | yes | yes * | yes ** | -createEventInteractively | interactive | yes | yes | yes ** | -createEventInteractivelyWithOptions | interactive | yes | yes | yes ** | -findEvent | | yes | yes | | -findEventWithOptions | | yes | yes | | -listEventsInRange | | yes | yes | | -listCalendars | | yes | yes | | -findAllEventsInNamedCalendars | | yes | | | -modifyEvent | | yes | | | -modifyEventWithOptions | | yes | | | -deleteEvent | | yes | yes | | -deleteEventFromNamedCalendar | | yes | | | -deleteEventById | | yes | yes | | -openCalendar | | yes | yes | | - -* \* on Android < 4 dialog is shown -* \** only interactively on windows mobile +| Operation | Comment | iOS | Android | Windows | +| ----------------------------------- | ----------- | --- | ------- | -------- | +| createCalendar | | yes | yes | | +| deleteCalendar | | yes | yes | | +| createEvent | silent | yes | yes \* | yes \*\* | +| createEventWithOptions | silent | yes | yes \* | yes \*\* | +| createEventInteractively | interactive | yes | yes | yes \*\* | +| createEventInteractivelyWithOptions | interactive | yes | yes | yes \*\* | +| findEvent | | yes | yes | | +| findEventWithOptions | | yes | yes | | +| listEventsInRange | | yes | yes | | +| listCalendars | | yes | yes | | +| findAllEventsInNamedCalendars | | yes | | | +| modifyEvent | | yes | | | +| modifyEventWithOptions | | yes | | | +| deleteEvent | | yes | yes | | +| deleteEventFromNamedCalendar | | yes | | | +| deleteEventById | | yes | yes | | +| openCalendar | | yes | yes | | + +- \* on Android < 4 dialog is shown +- \*\* only interactively on windows mobile Basic operations, you'll want to copy-paste this for testing purposes: + ```js - // prep some variables - var startDate = new Date(2015,2,15,18,30,0,0,0); // beware: month 0 = january, 11 = december - var endDate = new Date(2015,2,15,19,30,0,0,0); - var title = "My nice event"; - var eventLocation = "Home"; - var notes = "Some notes about this event."; - var success = function(message) { alert("Success: " + JSON.stringify(message)); }; - var error = function(message) { alert("Error: " + message); }; - - // create a calendar (iOS only for now) - window.plugins.calendar.createCalendar(calendarName,success,error); - // if you want to create a calendar with a specific color, pass in a JS object like this: - var createCalOptions = window.plugins.calendar.getCreateCalendarOptions(); - createCalOptions.calendarName = "My Cal Name"; - createCalOptions.calendarColor = "#FF0000"; // an optional hex color (with the # char), default is null, so the OS picks a color - window.plugins.calendar.createCalendar(createCalOptions,success,error); - - // delete a calendar - window.plugins.calendar.deleteCalendar(calendarName,success,error); - - // create an event silently (on Android < 4 an interactive dialog is shown) - window.plugins.calendar.createEvent(title,eventLocation,notes,startDate,endDate,success,error); - - // create an event silently (on Android < 4 an interactive dialog is shown which doesn't use this options) with options: - var calOptions = window.plugins.calendar.getCalendarOptions(); // grab the defaults - calOptions.firstReminderMinutes = 120; // default is 60, pass in null for no reminder (alarm) - calOptions.secondReminderMinutes = 5; - - // Added these options in version 4.2.4: - calOptions.recurrence = "monthly"; // supported are: daily, weekly, monthly, yearly - calOptions.recurrenceEndDate = new Date(2016,10,1,0,0,0,0,0); // leave null to add events into infinity and beyond - calOptions.calendarName = "MyCreatedCalendar"; // iOS only - calOptions.calendarId = 1; // Android only, use id obtained from listCalendars() call which is described below. This will be ignored on iOS in favor of calendarName and vice versa. Default: 1. - - // This is new since 4.2.7: - calOptions.recurrenceInterval = 2; // once every 2 months in this case, default: 1 - - // And the URL can be passed since 4.3.2 (will be appended to the notes on Android as there doesn't seem to be a sep field) - calOptions.url = "https://www.google.com"; - - // on iOS the success handler receives the event ID (since 4.3.6) - window.plugins.calendar.createEventWithOptions(title,eventLocation,notes,startDate,endDate,calOptions,success,error); - - // create an event interactively - window.plugins.calendar.createEventInteractively(title,eventLocation,notes,startDate,endDate,success,error); - - // create an event interactively with the calOptions object as shown above - window.plugins.calendar.createEventInteractivelyWithOptions(title,eventLocation,notes,startDate,endDate,calOptions,success,error); - - // create an event in a named calendar (iOS only, deprecated, use createEventWithOptions instead) - window.plugins.calendar.createEventInNamedCalendar(title,eventLocation,notes,startDate,endDate,calendarName,success,error); - - // find events (on iOS this includes a list of attendees (if any)) - window.plugins.calendar.findEvent(title,eventLocation,notes,startDate,endDate,success,error); - - // if you need to find events in a specific calendar, use this one. All options are currently ignored when finding events, except for the calendarName. - var calOptions = window.plugins.calendar.getCalendarOptions(); - calOptions.calendarName = "MyCreatedCalendar"; // iOS only - calOptions.id = "D9B1D85E-1182-458D-B110-4425F17819F1"; // if not found, we try matching against title, etc - window.plugins.calendar.findEventWithOptions(title,eventLocation,notes,startDate,endDate,calOptions,success,error); - - // list all events in a date range (only supported on Android for now) - window.plugins.calendar.listEventsInRange(startDate,endDate,success,error); - - // list all calendar names - returns this JS Object to the success callback: [{"id":"1", "name":"first"}, ..] - window.plugins.calendar.listCalendars(success,error); - - // find all _future_ events in the first calendar with the specified name (iOS only for now, this includes a list of attendees (if any)) - window.plugins.calendar.findAllEventsInNamedCalendar(calendarName,success,error); - - // change an event (iOS only for now) - var newTitle = "New title!"; - window.plugins.calendar.modifyEvent(title,eventLocation,notes,startDate,endDate,newTitle,eventLocation,notes,startDate,endDate,success,error); - - // or to add a reminder, make it recurring, change the calendar, or the url, use this one: - var filterOptions = window.plugins.calendar.getCalendarOptions(); // or {} or null for the defaults - filterOptions.calendarName = "Bla"; // iOS only - filterOptions.id = "D9B1D85E-1182-458D-B110-4425F17819F1"; // iOS only, get it from createEventWithOptions (if not found, we try matching against title, etc) - var newOptions = window.plugins.calendar.getCalendarOptions(); - newOptions.calendaName = "New Bla"; // make sure this calendar exists before moving the event to it - // not passing in reminders will wipe them from the event. To wipe the default first reminder (60), set it to null. - newOptions.firstReminderMinutes = 120; - window.plugins.calendar.modifyEventWithOptions(title,eventLocation,notes,startDate,endDate,newTitle,eventLocation,notes,startDate,endDate,filterOptions,newOptions,success,error); - - // delete an event (you can pass nulls for irrelevant parameters). The dates are mandatory and represent a date range to delete events in. - // note that on iOS there is a bug where the timespan must not be larger than 4 years, see issue 102 for details.. call this method multiple times if need be - // since 4.3.0 you can match events starting with a prefix title, so if your event title is 'My app - cool event' then 'My app -' will match. - window.plugins.calendar.deleteEvent(newTitle,eventLocation,notes,startDate,endDate,success,error); - - // delete an event, as above, but for a specific calendar (iOS only) - window.plugins.calendar.deleteEventFromNamedCalendar(newTitle,eventLocation,notes,startDate,endDate,calendarName,success,error); - - // delete an event by id. If the event has recurring instances, all will be deleted unless `fromDate` is specified, which will delete from that date onward. (iOS and android only) - window.plugins.calendar.deleteEventById(id,fromDate,success,error); - - // open the calendar app (added in 4.2.8): - // - open it at 'today' - window.plugins.calendar.openCalendar(); - // - open at a specific date, here today + 3 days - var d = new Date(new Date().getTime() + 3*24*60*60*1000); - window.plugins.calendar.openCalendar(d, success, error); // callbacks are optional +// prep some variables +var startDate = new Date(2015, 2, 15, 18, 30, 0, 0, 0); // beware: month 0 = january, 11 = december +var endDate = new Date(2015, 2, 15, 19, 30, 0, 0, 0); +var title = 'My nice event'; +var eventLocation = 'Home'; +var notes = 'Some notes about this event.'; +var success = function (message) { + alert('Success: ' + JSON.stringify(message)); +}; +var error = function (message) { + alert('Error: ' + message); +}; + +// create a calendar (iOS only for now) +window.plugins.calendar.createCalendar(calendarName, success, error); +// if you want to create a calendar with a specific color, pass in a JS object like this: +var createCalOptions = window.plugins.calendar.getCreateCalendarOptions(); +createCalOptions.calendarName = 'My Cal Name'; +createCalOptions.calendarColor = '#FF0000'; // an optional hex color (with the # char), default is null, so the OS picks a color +window.plugins.calendar.createCalendar(createCalOptions, success, error); + +// delete a calendar +window.plugins.calendar.deleteCalendar(calendarName, success, error); + +// create an event silently (on Android < 4 an interactive dialog is shown) +window.plugins.calendar.createEvent( + title, + eventLocation, + notes, + startDate, + endDate, + success, + error +); + +// create an event silently (on Android < 4 an interactive dialog is shown which doesn't use this options) with options: +var calOptions = window.plugins.calendar.getCalendarOptions(); // grab the defaults +calOptions.firstReminderMinutes = 120; // default is 60, pass in null for no reminder (alarm) +calOptions.secondReminderMinutes = 5; + +// Added these options in version 4.2.4: +calOptions.recurrence = 'monthly'; // supported are: daily, weekly, monthly, yearly +calOptions.recurrenceEndDate = new Date(2016, 10, 1, 0, 0, 0, 0, 0); // leave null to add events into infinity and beyond +calOptions.calendarName = 'MyCreatedCalendar'; // iOS only +calOptions.calendarId = 1; // Android only, use id obtained from listCalendars() call which is described below. This will be ignored on iOS in favor of calendarName and vice versa. Default: 1. + +// This is new since 4.2.7: +calOptions.recurrenceInterval = 2; // once every 2 months in this case, default: 1 + +// And the URL can be passed since 4.3.2 (will be appended to the notes on Android as there doesn't seem to be a sep field) +calOptions.url = 'https://www.google.com'; + +// on iOS the success handler receives the event ID (since 4.3.6) +window.plugins.calendar.createEventWithOptions( + title, + eventLocation, + notes, + startDate, + endDate, + calOptions, + success, + error +); + +// create an event interactively +window.plugins.calendar.createEventInteractively( + title, + eventLocation, + notes, + startDate, + endDate, + success, + error +); + +// create an event interactively with the calOptions object as shown above +window.plugins.calendar.createEventInteractivelyWithOptions( + title, + eventLocation, + notes, + startDate, + endDate, + calOptions, + success, + error +); + +// create an event in a named calendar (iOS only, deprecated, use createEventWithOptions instead) +window.plugins.calendar.createEventInNamedCalendar( + title, + eventLocation, + notes, + startDate, + endDate, + calendarName, + success, + error +); + +// find events (on iOS this includes a list of attendees (if any)) +window.plugins.calendar.findEvent( + title, + eventLocation, + notes, + startDate, + endDate, + success, + error +); + +// if you need to find events in a specific calendar, use this one. All options are currently ignored when finding events, except for the calendarName. +var calOptions = window.plugins.calendar.getCalendarOptions(); +calOptions.calendarName = 'MyCreatedCalendar'; // iOS only +calOptions.id = 'D9B1D85E-1182-458D-B110-4425F17819F1'; // if not found, we try matching against title, etc +window.plugins.calendar.findEventWithOptions( + title, + eventLocation, + notes, + startDate, + endDate, + calOptions, + success, + error +); + +// list all events in a date range (only supported on Android for now) +window.plugins.calendar.listEventsInRange(startDate, endDate, success, error); + +// list all calendar names - returns this JS Object to the success callback: [{"id":"1", "name":"first"}, ..] +window.plugins.calendar.listCalendars(success, error); + +// find all _future_ events in the first calendar with the specified name (iOS only for now, this includes a list of attendees (if any)) +window.plugins.calendar.findAllEventsInNamedCalendar( + calendarName, + success, + error +); + +// change an event (iOS only for now) +var newTitle = 'New title!'; +window.plugins.calendar.modifyEvent( + title, + eventLocation, + notes, + startDate, + endDate, + newTitle, + eventLocation, + notes, + startDate, + endDate, + success, + error +); + +// or to add a reminder, make it recurring, change the calendar, or the url, use this one: +var filterOptions = window.plugins.calendar.getCalendarOptions(); // or {} or null for the defaults +filterOptions.calendarName = 'Bla'; // iOS only +filterOptions.id = 'D9B1D85E-1182-458D-B110-4425F17819F1'; // iOS only, get it from createEventWithOptions (if not found, we try matching against title, etc) +var newOptions = window.plugins.calendar.getCalendarOptions(); +newOptions.calendaName = 'New Bla'; // make sure this calendar exists before moving the event to it +// not passing in reminders will wipe them from the event. To wipe the default first reminder (60), set it to null. +newOptions.firstReminderMinutes = 120; +window.plugins.calendar.modifyEventWithOptions( + title, + eventLocation, + notes, + startDate, + endDate, + newTitle, + eventLocation, + notes, + startDate, + endDate, + filterOptions, + newOptions, + success, + error +); + +// delete an event (you can pass nulls for irrelevant parameters). The dates are mandatory and represent a date range to delete events in. +// note that on iOS there is a bug where the timespan must not be larger than 4 years, see issue 102 for details.. call this method multiple times if need be +// since 4.3.0 you can match events starting with a prefix title, so if your event title is 'My app - cool event' then 'My app -' will match. +window.plugins.calendar.deleteEvent( + newTitle, + eventLocation, + notes, + startDate, + endDate, + success, + error +); + +// delete an event, as above, but for a specific calendar (iOS only) +window.plugins.calendar.deleteEventFromNamedCalendar( + newTitle, + eventLocation, + notes, + startDate, + endDate, + calendarName, + success, + error +); + +// delete an event by id. If the event has recurring instances, all will be deleted unless `fromDate` is specified, which will delete from that date onward. (iOS and android only) +window.plugins.calendar.deleteEventById(id, fromDate, success, error); + +// open the calendar app (added in 4.2.8): +// - open it at 'today' +window.plugins.calendar.openCalendar(); +// - open at a specific date, here today + 3 days +var d = new Date(new Date().getTime() + 3 * 24 * 60 * 60 * 1000); +window.plugins.calendar.openCalendar(d, success, error); // callbacks are optional ``` Creating an all day event: + ```js - // set the startdate to midnight and set the enddate to midnight the next day - var startDate = new Date(2014,2,15,0,0,0,0,0); - var endDate = new Date(2014,2,16,0,0,0,0,0); +// set the startdate to midnight and set the enddate to midnight the next day +var startDate = new Date(2014, 2, 15, 0, 0, 0, 0, 0); +var endDate = new Date(2014, 2, 16, 0, 0, 0, 0, 0); ``` Creating an event for 3 full days + ```js - // set the startdate to midnight and set the enddate to midnight 3 days later - var startDate = new Date(2014,2,24,0,0,0,0,0); - var endDate = new Date(2014,2,27,0,0,0,0,0); +// set the startdate to midnight and set the enddate to midnight 3 days later +var startDate = new Date(2014, 2, 24, 0, 0, 0, 0, 0); +var endDate = new Date(2014, 2, 27, 0, 0, 0, 0, 0); ``` Example Response IOS getCalendarOptions + ```js { calendarId: null, @@ -292,6 +423,7 @@ url: null ``` Exmaple Response IOS Calendars + ```js { id: "258B0D99-394C-4189-9250-9488F75B399D", @@ -301,6 +433,7 @@ type: "Local" ``` Exmaple Response IOS Event + ```js { calendar: "Kalender", @@ -315,6 +448,7 @@ title: "myEvent" ``` ### Android 6 (M) Permissions + On Android 6 you need to request permission to use the Calendar at runtime when targeting API level 23+. Even if the `uses-permission` tags for the Calendar are present in `AndroidManifest.xml`. @@ -331,20 +465,18 @@ Note that the hasPermission functions will return true when: - You've already granted permission. ```js - // again, this is no longer needed with plugin version 4.5.0 and up - function hasReadWritePermission() { - window.plugins.calendar.hasReadWritePermission( - function(result) { - // if this is 'false' you probably want to call 'requestReadWritePermission' now - alert(result); - } - ) - } - - function requestReadWritePermission() { - // no callbacks required as this opens a popup which returns async - window.plugins.calendar.requestReadWritePermission(); - } +// again, this is no longer needed with plugin version 4.5.0 and up +function hasReadWritePermission() { + window.plugins.calendar.hasReadWritePermission(function (result) { + // if this is 'false' you probably want to call 'requestReadWritePermission' now + alert(result); + }); +} + +function requestReadWritePermission() { + // no callbacks required as this opens a popup which returns async + window.plugins.calendar.requestReadWritePermission(); +} ``` There are similar methods for Read and Write access only (`hasReadPermission`, etc), @@ -356,6 +488,7 @@ If permission is needed the plugin will now show the permission request popup. The user will then need to allow access and invoke the same method again after doing so. ## 4. Promises + If you like to use promises instead of callbacks, or struggle to create a lot of events asynchronously with this plugin then I encourage you to take a look at [this awesome wrapper](https://github.com/poetic-labs/native-calender-api) for @@ -364,10 +497,11 @@ this plugin. Kudos to [John Rodney](https://github.com/JohnRodney) for this piec ## 5. Credits This plugin was enhanced for Plugman / PhoneGap Build by [Eddy Verbruggen](http://www.x-services.nl). I fixed some issues in the native code (mainly for iOS) and changed the JS-Native functions a little in order to make a universal JS API for both platforms. -* Inspired by [this nice blog of Devgirl](http://devgirl.org/2013/07/17/tutorial-how-to-write-a-phonegap-plugin-for-android/). -* Credits for the original iOS code go to [Felix Montanez](https://github.com/felixactv8/Phonegap-Calendar-Plugin-ios). -* Credits for the original Android code go to [Ten Forward Consulting](https://github.com/tenforwardconsulting/Phonegap-Calendar-Plugin-android) and [twistandshout](https://github.com/twistandshout/phonegap-calendar-plugin). -* Special thanks to [four32c.com](http://four32c.com) for sponsoring part of the implementation, while keeping the plugin opensource. + +- Inspired by [this nice blog of Devgirl](http://devgirl.org/2013/07/17/tutorial-how-to-write-a-phonegap-plugin-for-android/). +- Credits for the original iOS code go to [Felix Montanez](https://github.com/felixactv8/Phonegap-Calendar-Plugin-ios). +- Credits for the original Android code go to [Ten Forward Consulting](https://github.com/tenforwardconsulting/Phonegap-Calendar-Plugin-android) and [twistandshout](https://github.com/twistandshout/phonegap-calendar-plugin). +- Special thanks to [four32c.com](http://four32c.com) for sponsoring part of the implementation, while keeping the plugin opensource. ## 6. License diff --git a/src/ios/Calendar.h b/src/ios/Calendar.h index bd61a402..cf25f619 100644 --- a/src/ios/Calendar.h +++ b/src/ios/Calendar.h @@ -5,43 +5,44 @@ @interface Calendar : CDVPlugin -@property (nonatomic, retain) EKEventStore* eventStore; -@property (nonatomic, copy) NSString *interactiveCallbackId; +@property(nonatomic, retain) EKEventStore *eventStore; +@property(nonatomic, copy) NSString *interactiveCallbackId; -- (void)initEventStoreWithCalendarCapabilities; +- (void)initEventStoreWithFullCalendarCapabilities; +- (void)initEventStoreWithWriteCalendarCapabilities; --(NSArray*)findEKEventsWithTitle: (NSString *)title - location: (NSString *)location - notes: (NSString *)notes - startDate: (NSDate *)startDate - endDate: (NSDate *)endDate - calendars: (NSArray *)calendars; +- (NSArray *)findEKEventsWithTitle:(NSString *)title + location:(NSString *)location + notes:(NSString *)notes + startDate:(NSDate *)startDate + endDate:(NSDate *)endDate + calendars:(NSArray *)calendars; -- (void)hasReadPermission:(CDVInvokedUrlCommand*)command; -- (void)requestReadPermission:(CDVInvokedUrlCommand*)command; +- (void)hasReadPermission:(CDVInvokedUrlCommand *)command; +- (void)requestReadPermission:(CDVInvokedUrlCommand *)command; -- (void)hasWritePermission:(CDVInvokedUrlCommand*)command; -- (void)requestWritePermission:(CDVInvokedUrlCommand*)command; +- (void)hasWritePermission:(CDVInvokedUrlCommand *)command; +- (void)requestWritePermission:(CDVInvokedUrlCommand *)command; -- (void)hasReadWritePermission:(CDVInvokedUrlCommand*)command; -- (void)requestReadWritePermission:(CDVInvokedUrlCommand*)command; +- (void)hasReadWritePermission:(CDVInvokedUrlCommand *)command; +- (void)requestReadWritePermission:(CDVInvokedUrlCommand *)command; -- (void)openCalendar:(CDVInvokedUrlCommand*)command; -- (void)createCalendar:(CDVInvokedUrlCommand*)command; -- (void)deleteCalendar:(CDVInvokedUrlCommand*)command; +- (void)openCalendar:(CDVInvokedUrlCommand *)command; +- (void)createCalendar:(CDVInvokedUrlCommand *)command; +- (void)deleteCalendar:(CDVInvokedUrlCommand *)command; -- (void)createEventWithOptions:(CDVInvokedUrlCommand*)command; -- (void)createEventInteractively:(CDVInvokedUrlCommand*)command; -- (void)modifyEventWithOptions:(CDVInvokedUrlCommand*)command; +- (void)createEventWithOptions:(CDVInvokedUrlCommand *)command; +- (void)createEventInteractively:(CDVInvokedUrlCommand *)command; +- (void)modifyEventWithOptions:(CDVInvokedUrlCommand *)command; -- (void)findEventWithOptions:(CDVInvokedUrlCommand*)command; -- (void)findAllEventsInNamedCalendar:(CDVInvokedUrlCommand*)command; +- (void)findEventWithOptions:(CDVInvokedUrlCommand *)command; +- (void)findAllEventsInNamedCalendar:(CDVInvokedUrlCommand *)command; -- (void)listCalendars:(CDVInvokedUrlCommand*)command; -- (void)deleteEvent:(CDVInvokedUrlCommand*)command; -- (void)deleteEventFromNamedCalendar:(CDVInvokedUrlCommand*)command; -- (void)deleteEventFromCalendar:(CDVInvokedUrlCommand*)command calendar:(EKCalendar*)calendar; -- (void)deleteEventById:(CDVInvokedUrlCommand*)command; -- (void)eventEditViewController:(EKEventEditViewController*)controller didCompleteWithAction:(EKEventEditViewAction) action; +- (void)listCalendars:(CDVInvokedUrlCommand *)command; +- (void)deleteEvent:(CDVInvokedUrlCommand *)command; +- (void)deleteEventFromNamedCalendar:(CDVInvokedUrlCommand *)command; +- (void)deleteEventFromCalendar:(CDVInvokedUrlCommand *)command calendar:(EKCalendar *)calendar; +- (void)deleteEventById:(CDVInvokedUrlCommand *)command; +- (void)eventEditViewController:(EKEventEditViewController *)controller didCompleteWithAction:(EKEventEditViewAction)action; @end diff --git a/src/ios/Calendar.m b/src/ios/Calendar.m index 1bc29767..533662bd 100644 --- a/src/ios/Calendar.m +++ b/src/ios/Calendar.m @@ -12,38 +12,102 @@ @implementation Calendar #pragma mark Initialization functions - (void) pluginInitialize { - [self initEventStoreWithCalendarCapabilities]; } -- (void) initEventStoreWithCalendarCapabilities { +/** + * Init event store with read and write capabilities. + * + * Note: This will overwrite the existing event store. + */ +- (void) initEventStoreWithFullCalendarCapabilities { + NSLog(@"Initialising calendar event store with full access."); + EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init]; + __block BOOL accessGranted = NO; + dispatch_semaphore_t sema = dispatch_semaphore_create(0); if (@available(iOS 17.0, *)) { [eventStoreCandidate requestFullAccessToEventsWithCompletion:^(BOOL granted, NSError *error) { - if (granted) { - self.eventStore = eventStoreCandidate; - NSLog(@"Full access to the event store granted and eventStore initialized."); - } else { - NSLog(@"Access to the event store not granted. Error: %@", error.localizedDescription); + accessGranted = granted; + + if (!accessGranted) { + NSLog(@"Full access to the event store not granted. Error: %@", error.localizedDescription); } + + dispatch_semaphore_signal(sema); }]; } else { - __block BOOL accessGranted = NO; + // iOS 16 or lower + if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) { + [eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { + accessGranted = granted; + + if (!accessGranted) { + NSLog(@"Full access to the event store not granted. Error: %@", error.localizedDescription); + } + dispatch_semaphore_signal(sema); + }]; + } else { + // iOS 5 or lower + accessGranted = YES; + dispatch_semaphore_signal(sema); + } + } + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + + if (accessGranted) { + self.eventStore = eventStoreCandidate; + NSLog(@"Full access to the event store granted and eventStore initialized."); + } +} + +/** + * Init event store with write capabilities only. + * + * Note: This will overwrite the existing event store, so you may not be able to read events after requesting write + * only permission, even if you have previously requested read and write permission. + */ +- (void) initEventStoreWithWriteCalendarCapabilities { + NSLog(@"Initialising calendar event store with write access."); + + EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init]; + __block BOOL accessGranted = NO; + + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + if (@available(iOS 17.0, *)) { + [eventStoreCandidate requestWriteOnlyAccessToEventsWithCompletion:^(BOOL granted, NSError *error) { + accessGranted = granted; + + if (!accessGranted) { + NSLog(@"Write access to the event store not granted. Error: %@", error.localizedDescription); + } + + dispatch_semaphore_signal(sema); + }]; + } else { + // iOS 16 or lower if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) { - dispatch_semaphore_t sema = dispatch_semaphore_create(0); [eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { accessGranted = granted; + + if (!accessGranted) { + NSLog(@"Write access to the event store not granted after requesting full access for iOS <= 16. Error: %@", error.localizedDescription); + } + dispatch_semaphore_signal(sema); }]; - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - } else { // we're on iOS 5 or older + } else { + // iOS 5 or lower accessGranted = YES; + dispatch_semaphore_signal(sema); } } + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if (accessGranted) { self.eventStore = eventStoreCandidate; + NSLog(@"Write access to the event store granted and eventStore initialized."); } } @@ -1056,7 +1120,7 @@ - (void)hasReadPermission:(CDVInvokedUrlCommand*)command { } - (void)requestReadPermission:(CDVInvokedUrlCommand*)command { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestCalendarAccess]]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestFullCalendarAccess]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @@ -1066,7 +1130,7 @@ - (void)hasWritePermission:(CDVInvokedUrlCommand*)command { } - (void)requestWritePermission:(CDVInvokedUrlCommand*)command { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestCalendarAccess]]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestWriteCalendarAccess]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @@ -1076,14 +1140,18 @@ - (void)hasReadWritePermission:(CDVInvokedUrlCommand*)command { } - (void)requestReadWritePermission:(CDVInvokedUrlCommand*)command { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestCalendarAccess]]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestFullCalendarAccess]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } --(CDVCommandStatus)requestCalendarAccess{ - [self initEventStoreWithCalendarCapabilities]; - return (self.eventStore != nil) ? CDVCommandStatus_OK : CDVCommandStatus_ERROR; +-(CDVCommandStatus)requestFullCalendarAccess{ + [self initEventStoreWithFullCalendarCapabilities]; + return (self.eventStore != nil) ? CDVCommandStatus_OK : CDVCommandStatus_ERROR; } +-(CDVCommandStatus)requestWriteCalendarAccess{ + [self initEventStoreWithWriteCalendarCapabilities]; + return (self.eventStore != nil) ? CDVCommandStatus_OK : CDVCommandStatus_ERROR; +} -@end +@end \ No newline at end of file From cff2cca3637b84839e55eeebbc43bf9fe77cf669 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 5 Oct 2023 13:47:12 +0200 Subject: [PATCH 5/5] fix mt! allday event duration --- .../xservices/plugins/accessor/AbstractCalendarAccessor.java | 1 + src/ios/Calendar.m | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java b/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java index 33a99a6c..6e5e8581 100644 --- a/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java +++ b/src/android/nl/xservices/plugins/accessor/AbstractCalendarAccessor.java @@ -612,6 +612,7 @@ public String createEvent(Uri eventsUri, String title, long startTime, long endT // accounts for daylight savings time values.put(Events.EVENT_TIMEZONE, "UTC"); values.put(Events.DTSTART, startTime + TimeZone.getDefault().getOffset(startTime)); + endTime = endTime + (24 * 60 * 60 * 1000); values.put(Events.DTEND, endTime + TimeZone.getDefault().getOffset(endTime)); } else { values.put(Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); diff --git a/src/ios/Calendar.m b/src/ios/Calendar.m index 533662bd..41cd3ffb 100644 --- a/src/ios/Calendar.m +++ b/src/ios/Calendar.m @@ -627,6 +627,7 @@ - (void)createEventWithOptions:(CDVInvokedUrlCommand*)command { NSNumber* recurrenceIntervalAmount = [calOptions objectForKey:@"recurrenceInterval"]; NSString* calendarName = [calOptions objectForKey:@"calendarName"]; NSString* url = [calOptions objectForKey:@"url"]; + NSObject* allDay = [options objectForKey:@"allday"]; [self.commandDelegate runInBackground: ^{ EKEvent *myEvent = [EKEvent eventWithEventStore: self.eventStore]; @@ -650,10 +651,10 @@ - (void)createEventWithOptions:(CDVInvokedUrlCommand*)command { myEvent.startDate = myStartDate; int duration = _endInterval - _startInterval; - int moduloDay = duration % (60*60*24); + int moduloDay = duration % (60 * 60 * 24); if (moduloDay == 0) { myEvent.allDay = YES; - myEvent.endDate = [NSDate dateWithTimeIntervalSince1970:_endInterval-1]; + myEvent.endDate = [NSDate dateWithTimeIntervalSince1970:_endInterval + (24 * 60 * 60) - 1]; } else { myEvent.endDate = [NSDate dateWithTimeIntervalSince1970:_endInterval]; }