diff --git a/app/src/main/java/net/moasdawiki/app/CalendarSyncAdapter.java b/app/src/main/java/net/moasdawiki/app/CalendarSyncAdapter.java index 10a5880..a13ded6 100644 --- a/app/src/main/java/net/moasdawiki/app/CalendarSyncAdapter.java +++ b/app/src/main/java/net/moasdawiki/app/CalendarSyncAdapter.java @@ -48,8 +48,10 @@ import net.moasdawiki.util.PathUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.TimeZone; @@ -71,8 +73,21 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { private static final Uri EVENT_URI = CalendarContract.Events. CONTENT_URI; private static final Uri REMINDER_URI = CalendarContract.Reminders.CONTENT_URI; + /** + * Alert event 4h before it starts, i.e. at 8pm in the evening before the event day. + */ private static final int REMINDER_BEFORE_EVENT_MINUTES = 4 * 60; + /** + * Import maximum 100 events. If there are more events found, take the 100 upcoming events + * sorted by date. + * + * Android has a global event limitation of 500 events. If more than 500 events are created, + * Android throws errors with message "Maximum limit of concurrent alarms 500 reached" and + * ignores some events. + */ + private static final int MAX_EVENT_COUNT = 100; + private final ContentResolver contentResolver; @SuppressWarnings("SameParameterValue") @@ -84,13 +99,19 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { Log.d(TAG, "Begin of onPerformSync()"); - List events = getWikiEvents(); + // collect and filter events + List rawEvents = getWikiEvents(); + fillEmptyDateFields(rawEvents); + List events = filterEvents(rawEvents); + + // update Android calendar String calendarId = createCalendar(); if (calendarId != null) { deleteAllEvents(calendarId); addEvents(calendarId, events); } + // inform user about completed update Handler handler = new Handler(Looper.getMainLooper()); handler.post(() -> { Toast toast = Toast.makeText(getContext(), R.string.calendar_sync_finished, Toast.LENGTH_SHORT); @@ -116,6 +137,65 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { return events; } + /** + * Fills empty day, month, and year fields in all events. + */ + private void fillEmptyDateFields(@NotNull List events) { + TimeZone utc = TimeZone.getTimeZone("UTC"); + Calendar calendar = Calendar.getInstance(utc); + for (TerminTransformer.Event event : events) { + if (event.dateFields.day == null) { + event.dateFields.day = 1; + } + if (event.dateFields.month == null) { + event.dateFields.month = 1; + } + if (event.dateFields.year == null) { + event.dateFields.year = calendar.get(Calendar.YEAR); + } + } + } + + /** + * Reduces the number of events if more than MAX_EVENT_COUNT, only keep events in the near future. + */ + @NotNull + private List filterEvents(@NotNull List rawEvents) { + if (rawEvents.size() <= MAX_EVENT_COUNT) { + return rawEvents; + } + + // sort by month and day; + // ignore year because events repeat every year + List sortedEvents = new ArrayList<>(rawEvents); + sortedEvents.sort(Comparator.comparingInt((TerminTransformer.Event event) -> event.dateFields.month).thenComparingInt(event -> event.dateFields.day)); + + // keep next MAX_EVENT_COUNT events from today onwards + List result = new ArrayList<>(MAX_EVENT_COUNT); + // find first event in future + TimeZone utc = TimeZone.getTimeZone("UTC"); + Calendar calendar = Calendar.getInstance(utc); + int index = 0; + while (index < sortedEvents.size() - 1 + && (sortedEvents.get(index).dateFields.month < calendar.get(Calendar.MONTH) + 1 + || sortedEvents.get(index).dateFields.month == calendar.get(Calendar.MONTH) + 1 + && sortedEvents.get(index).dateFields.day < calendar.get(Calendar.DAY_OF_MONTH))) { + index++; + } + // copy events until end of year + while (result.size() < MAX_EVENT_COUNT && index < sortedEvents.size() - 1) { + result.add(sortedEvents.get(index)); + index++; + } + // copy events in new year + index = 0; + while (result.size() < MAX_EVENT_COUNT && index < sortedEvents.size() - 1) { + result.add(sortedEvents.get(index)); + index++; + } + return result; + } + @NotNull private Uri buildUri(@NotNull Uri uri) { return uri.buildUpon() @@ -155,7 +235,6 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { cv.put(CalendarContract.Calendars.NAME, CALENDAR_NAME); String displayName = getContext().getString(R.string.calendar_display_name); cv.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, displayName); - //cv.put(CalendarContract.Calendars.CALENDAR_COLOR, 0xEA8561); cv.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_READ); cv.put(CalendarContract.Calendars.OWNER_ACCOUNT, ACCOUNT_NAME); cv.put(CalendarContract.Calendars.VISIBLE, 1); @@ -191,8 +270,8 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { } /** - * Adds all events from the event list. The first occurrence is in the current year, the events - * are repeated every year. + * Adds up to 100 events from the event list. + * The first occurrence is in the current year, the events are repeated every year. */ private void addEvents(@NotNull String calendarId, @NotNull List events) { Log.d(TAG, "Create calendar events"); @@ -214,7 +293,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { * Adds a single event to the calendar. */ @Nullable - private String addEvent(@NotNull String calendarId, @Nullable Integer day, @Nullable Integer month, @Nullable Integer year, @NotNull String title, @NotNull String description) { + private String addEvent(@NotNull String calendarId, int day, int month, int year, @NotNull String title, @NotNull String description) { Log.d(TAG, "Create calendar event: day=" + day + ", month=" + month + ", year=" + year + ", title=" + title + ", description=" + description); ContentValues cv = new ContentValues(); @@ -224,18 +303,8 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter { TimeZone utc = TimeZone.getTimeZone("UTC"); Calendar beginTime = Calendar.getInstance(utc); - if (day == null) { - day = 1; - } - if (month == null) { - month = 1; - } - if (year == null) { - year = beginTime.get(Calendar.YEAR); - } beginTime.clear(); beginTime.set(year, month - 1, day); - cv.put(CalendarContract.Events.DTSTART, beginTime.getTimeInMillis()); cv.put(CalendarContract.Events.DURATION, "PT1D"); cv.put(CalendarContract.Events.ALL_DAY, 1);