diff --git a/app/src/main/java/eu/faircode/email/CalendarHelper.java b/app/src/main/java/eu/faircode/email/CalendarHelper.java new file mode 100644 index 0000000000..54cc00be69 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/CalendarHelper.java @@ -0,0 +1,176 @@ +package eu.faircode.email; + +/* + This file is part of FairEmail. + + FairEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FairEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FairEmail. If not, see . + + Copyright 2018-2022 by Marcel Bokhorst (M66B) +*/ + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.CalendarContract; +import android.text.TextUtils; + +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import biweekly.ICalVersion; +import biweekly.component.VEvent; +import biweekly.io.TimezoneInfo; +import biweekly.io.WriteContext; +import biweekly.io.scribe.property.RecurrenceRuleScribe; +import biweekly.parameter.ParticipationStatus; +import biweekly.property.Attendee; +import biweekly.property.RecurrenceRule; +import biweekly.util.ICalDate; + +public class CalendarHelper { + static void insert(Context context, TimezoneInfo tz, VEvent event, + String selectedAccount, String selectedName, EntityMessage message) { + String summary = (event.getSummary() == null ? null : event.getSummary().getValue()); + String description = (event.getDescription() == null ? null : event.getDescription().getValue()); + String location = (event.getLocation() == null ? null : event.getLocation().getValue()); + + ICalDate start = (event.getDateStart() == null ? null : event.getDateStart().getValue()); + ICalDate end = (event.getDateEnd() == null ? null : event.getDateEnd().getValue()); + + String rrule = null; + RecurrenceRule recurrence = event.getRecurrenceRule(); + if (recurrence != null) { + RecurrenceRuleScribe scribe = new RecurrenceRuleScribe(); + WriteContext wcontext = new WriteContext(ICalVersion.V2_0, tz, null); + rrule = scribe.writeText(recurrence, wcontext); + } + + String uid = (event.getUid() == null ? null : event.getUid().getValue()); + + if (TextUtils.isEmpty(uid) || start == null || end == null) + return; + + ContentResolver resolver = context.getContentResolver(); + try (Cursor cursor = resolver.query(CalendarContract.Calendars.CONTENT_URI, + new String[]{CalendarContract.Calendars._ID}, + CalendarContract.Calendars.VISIBLE + " <> 0 AND " + + CalendarContract.Calendars.ACCOUNT_NAME + " = ?" + + (selectedName == null + ? "" + : " AND " + CalendarContract.Calendars.CALENDAR_DISPLAY_NAME + " = ?"), + selectedName == null + ? new String[]{selectedAccount} + : new String[]{selectedAccount, selectedName}, + null)) { + if (cursor.getCount() == 0) + EntityLog.log(context, EntityLog.Type.General, message, + "Account not found account=" + selectedAccount + ":" + selectedName); + + if (cursor.moveToNext()) { + // https://developer.android.com/guide/topics/providers/calendar-provider#add-event + // https://developer.android.com/reference/android/provider/CalendarContract.EventsColumns + ContentValues values = new ContentValues(); + values.put(CalendarContract.Events.CALENDAR_ID, cursor.getLong(0)); + if (!TextUtils.isEmpty(uid)) + values.put(CalendarContract.Events.UID_2445, uid); + values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); + values.put(CalendarContract.Events.DTSTART, start.getTime()); + values.put(CalendarContract.Events.DTEND, end.getTime()); + if (rrule != null) + values.put(CalendarContract.Events.RRULE, rrule); + if (!TextUtils.isEmpty(summary)) + values.put(CalendarContract.Events.TITLE, summary); + if (!TextUtils.isEmpty(description)) + values.put(CalendarContract.Events.DESCRIPTION, description); + if (!TextUtils.isEmpty(location)) + values.put(CalendarContract.Events.EVENT_LOCATION, location); + values.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_TENTATIVE); + + Uri uri = resolver.insert(CalendarContract.Events.CONTENT_URI, values); + long eventId = Long.parseLong(uri.getLastPathSegment()); + EntityLog.log(context, EntityLog.Type.General, message, "Inserted event" + + " id=" + eventId + + " uid=" + uid + + " start=" + new Date(start.getTime()) + + " end=" + new Date(end.getTime()) + + " summary=" + summary); + } + } + } + + static void update(Context context, VEvent event, EntityMessage message) { + String uid = (event.getUid() == null ? null : event.getUid().getValue()); + if (TextUtils.isEmpty(uid)) + return; + + List attendees = event.getAttendees(); + if (attendees == null || attendees.size() == 0) + return; + + ParticipationStatus status = attendees.get(0).getParticipationStatus(); + if (!ParticipationStatus.ACCEPTED.equals(status) && + !ParticipationStatus.DECLINED.equals(status)) + return; + + ContentResolver resolver = context.getContentResolver(); + try (Cursor cursor = resolver.query(CalendarContract.Events.CONTENT_URI, + new String[]{CalendarContract.Events._ID}, + CalendarContract.Events.UID_2445 + " = ?", + new String[]{uid}, + null)) { + while (cursor.moveToNext()) { + long eventId = cursor.getLong(0); + + // https://developer.android.com/guide/topics/providers/calendar-provider#modify-calendar + Uri updateUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId); + ContentValues values = new ContentValues(); + if (ParticipationStatus.ACCEPTED.equals(status)) + values.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CONFIRMED); + else + values.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CANCELED); + int rows = resolver.update(updateUri, values, null, null); + + EntityLog.log(context, EntityLog.Type.General, message, + "Updated event id=" + eventId + " uid=" + uid + " rows=" + rows); + } + } + } + + static void delete(Context context, VEvent event, EntityMessage message) { + String uid = (event.getUid() == null ? null : event.getUid().getValue()); + if (TextUtils.isEmpty(uid)) + return; + + ContentResolver resolver = context.getContentResolver(); + try (Cursor cursor = resolver.query(CalendarContract.Events.CONTENT_URI, + new String[]{CalendarContract.Events._ID}, + CalendarContract.Events.UID_2445 + " = ? ", + new String[]{uid}, + null)) { + while (cursor.moveToNext()) { + long eventId = cursor.getLong(0); + + // https://developer.android.com/guide/topics/providers/calendar-provider#delete-event + Uri deleteUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId); + int rows = resolver.delete(deleteUri, null, null); + EntityLog.log(context, EntityLog.Type.General, message, + "Deleted event id=" + eventId + " uid=" + uid + " rows=" + rows); + } + } + } +} diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index c693757db8..a17b63fd18 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -22,15 +22,10 @@ package eu.faircode.email; import static android.system.OsConstants.ENOSPC; import android.Manifest; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; -import android.database.Cursor; import android.net.Uri; import android.os.Build; -import android.provider.CalendarContract; import android.system.ErrnoException; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -145,14 +140,9 @@ import javax.mail.internet.ParameterList; import javax.mail.internet.ParseException; import biweekly.Biweekly; -import biweekly.ICalVersion; import biweekly.ICalendar; import biweekly.component.VEvent; -import biweekly.io.WriteContext; -import biweekly.io.scribe.property.RecurrenceRuleScribe; import biweekly.property.Method; -import biweekly.property.RecurrenceRule; -import biweekly.util.ICalDate; public class MessageHelper { private boolean ensuredEnvelope = false; @@ -3885,126 +3875,43 @@ public class MessageHelper { EntityFolder.SYSTEM.equals(folder.type) || EntityFolder.USER.equals(folder.type))); - if (!permission || !received || account == null || account.calendar == null) + if (!permission || !received || account == null || account.calendar == null) { EntityLog.log(context, "Event not processed" + " permission=" + permission + " account=" + (account != null) + " calendar=" + (account == null ? null : account.calendar) + " folder=" + (folder != null) + ":" + (folder == null ? null : folder.type) + " received=" + received); - else { - File file = local.getFile(context); - ICalendar icalendar = Biweekly.parse(file).first(); - String method = (icalendar.getMethod() == null ? null : icalendar.getMethod().getValue()); + return; + } - VEvent event = icalendar.getEvents().get(0); + File file = local.getFile(context); + ICalendar icalendar = Biweekly.parse(file).first(); - String summary = (event.getSummary() == null ? null : event.getSummary().getValue()); - String description = (event.getDescription() == null ? null : event.getDescription().getValue()); - String location = (event.getLocation() == null ? null : event.getLocation().getValue()); + Method method = icalendar.getMethod(); + if (method == null) + return; - ICalDate start = (event.getDateStart() == null ? null : event.getDateStart().getValue()); - ICalDate end = (event.getDateEnd() == null ? null : event.getDateEnd().getValue()); + VEvent event = icalendar.getEvents().get(0); - String rrule = null; - RecurrenceRule recurrence = event.getRecurrenceRule(); - if (recurrence != null) { - RecurrenceRuleScribe scribe = new RecurrenceRuleScribe(); - WriteContext wcontext = new WriteContext(ICalVersion.V2_0, icalendar.getTimezoneInfo(), null); - rrule = scribe.writeText(recurrence, wcontext); + if (method.isRequest() || method.isCancel()) + CalendarHelper.delete(context, event, message); + + if (method.isRequest()) { + String selectedAccount; + String selectedName; + try { + JSONObject jselected = new JSONObject(account.calendar); + selectedAccount = jselected.getString("account"); + selectedName = jselected.getString("name"); + } catch (Throwable ex) { + Log.i(ex); + selectedAccount = account.calendar; + selectedName = null; } - String uid = (event.getUid() == null ? null : event.getUid().getValue()); - - EntityLog.log(context, EntityLog.Type.General, message, "Processing event" + - " uid=" + uid + - " method=" + method + - " start=" + (start != null) + - " end=" + (end != null) + - " rrule=" + rrule + - " calendar=" + account.calendar); - - if (!TextUtils.isEmpty(uid) && - icalendar.getMethod() != null && - start != null && end != null) { - ContentResolver resolver = context.getContentResolver(); - - if (icalendar.getMethod().isRequest() || icalendar.getMethod().isCancel()) - try (Cursor cursor = resolver.query(CalendarContract.Events.CONTENT_URI, - new String[]{CalendarContract.Events._ID}, - CalendarContract.Events.UID_2445 + " = ? ", - new String[]{uid}, - null)) { - while (cursor.moveToNext()) { - long eventId = cursor.getLong(0); - - // https://developer.android.com/guide/topics/providers/calendar-provider#delete-event - Uri deleteUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId); - int rows = resolver.delete(deleteUri, null, null); - EntityLog.log(context, EntityLog.Type.General, message, - "Deleted event id=" + eventId + " rows=" + rows); - } - } - - if (icalendar.getMethod().isRequest()) { - String selectedAccount; - String selectedName; - try { - JSONObject jselected = new JSONObject(account.calendar); - selectedAccount = jselected.getString("account"); - selectedName = jselected.getString("name"); - } catch (Throwable ex) { - Log.i(ex); - selectedAccount = account.calendar; - selectedName = null; - } - - try (Cursor cursor = resolver.query(CalendarContract.Calendars.CONTENT_URI, - new String[]{CalendarContract.Calendars._ID}, - CalendarContract.Calendars.VISIBLE + " <> 0 AND " + - CalendarContract.Calendars.ACCOUNT_NAME + " = ?" + - (selectedName == null - ? "" - : " AND " + CalendarContract.Calendars.CALENDAR_DISPLAY_NAME + " = ?"), - selectedName == null - ? new String[]{selectedAccount} - : new String[]{selectedAccount, selectedName}, - null)) { - if (cursor.getCount() == 0) - EntityLog.log(context, EntityLog.Type.General, message, - "Account not found username=" + account.user); - - if (cursor.moveToNext()) { - // https://developer.android.com/guide/topics/providers/calendar-provider#add-event - // https://developer.android.com/reference/android/provider/CalendarContract.EventsColumns - ContentValues values = new ContentValues(); - values.put(CalendarContract.Events.CALENDAR_ID, cursor.getLong(0)); - if (!TextUtils.isEmpty(uid)) - values.put(CalendarContract.Events.UID_2445, uid); - values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); - values.put(CalendarContract.Events.DTSTART, start.getTime()); - values.put(CalendarContract.Events.DTEND, end.getTime()); - if (rrule != null) - values.put(CalendarContract.Events.RRULE, rrule); - if (!TextUtils.isEmpty(summary)) - values.put(CalendarContract.Events.TITLE, summary); - if (!TextUtils.isEmpty(description)) - values.put(CalendarContract.Events.DESCRIPTION, description); - if (!TextUtils.isEmpty(location)) - values.put(CalendarContract.Events.EVENT_LOCATION, location); - values.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_TENTATIVE); - - Uri uri = resolver.insert(CalendarContract.Events.CONTENT_URI, values); - long eventId = Long.parseLong(uri.getLastPathSegment()); - EntityLog.log(context, EntityLog.Type.General, message, "Inserted event" + - " id=" + eventId + - " start=" + new Date(start.getTime()) + - " end=" + new Date(end.getTime()) + - " summary=" + summary); - } - } - } - } + CalendarHelper.insert(context, icalendar.getTimezoneInfo(), event, + selectedAccount, selectedName, message); } } catch (Throwable ex) { Log.w(ex); diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 204663374b..a2049cc19d 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -25,22 +25,16 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.database.Cursor; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; -import android.net.Uri; import android.os.PowerManager; import android.os.SystemClock; -import android.provider.CalendarContract; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -78,10 +72,7 @@ import javax.mail.internet.MimeMessage; import biweekly.Biweekly; import biweekly.ICalendar; import biweekly.component.VEvent; -import biweekly.parameter.ParticipationStatus; -import biweekly.property.Attendee; import biweekly.property.Method; -import biweekly.property.Uid; public class ServiceSend extends ServiceBase implements SharedPreferences.OnSharedPreferenceChangeListener { private TupleUnsent lastUnsent = null; @@ -900,40 +891,8 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar return; VEvent event = icalendar.getEvents().get(0); - Uid uid = event.getUid(); - List attendees = event.getAttendees(); - if (uid == null || attendees == null || attendees.size() == 0) - return; - - ParticipationStatus status = attendees.get(0).getParticipationStatus(); - if (!ParticipationStatus.ACCEPTED.equals(status) && - !ParticipationStatus.DECLINED.equals(status)) - return; - - ContentResolver resolver = getContentResolver(); - try (Cursor cursor = resolver.query(CalendarContract.Events.CONTENT_URI, - new String[]{CalendarContract.Events._ID}, - CalendarContract.Events.UID_2445 + " = ? ", - new String[]{uid.getValue()}, - null)) { - while (cursor.moveToNext()) { - long eventId = cursor.getLong(0); - - // https://developer.android.com/guide/topics/providers/calendar-provider#modify-calendar - Uri updateUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId); - ContentValues values = new ContentValues(); - if (ParticipationStatus.ACCEPTED.equals(status)) - values.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CONFIRMED); - else - values.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CANCELED); - int rows = resolver.update(updateUri, values, null, null); - - EntityMessage message = db.message().getMessage(sid); - EntityLog.log(this, EntityLog.Type.General, message, - "Updated event id=" + eventId + " rows=" + rows); - } - } - + EntityMessage message = db.message().getMessage(sid); + CalendarHelper.update(this, event, message); break; } catch (Throwable ex) { Log.e(ex);