diff --git a/app/src/fdroid/AndroidManifest.xml b/app/src/fdroid/AndroidManifest.xml
index 568aa2c751..ab44a8fe1b 100644
--- a/app/src/fdroid/AndroidManifest.xml
+++ b/app/src/fdroid/AndroidManifest.xml
@@ -14,6 +14,8 @@
+
+
diff --git a/app/src/github/AndroidManifest.xml b/app/src/github/AndroidManifest.xml
index abeb6c632f..f446bbe8c7 100644
--- a/app/src/github/AndroidManifest.xml
+++ b/app/src/github/AndroidManifest.xml
@@ -14,6 +14,8 @@
+
+
diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java
index 4fa792cb29..6358b090ca 100644
--- a/app/src/main/java/eu/faircode/email/Helper.java
+++ b/app/src/main/java/eu/faircode/email/Helper.java
@@ -428,6 +428,19 @@ public class Helper {
permissions.add(Manifest.permission.READ_CONTACTS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
permissions.add(Manifest.permission.POST_NOTIFICATIONS);
+
+ try {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_PERMISSIONS);
+ for (int i = 0; i < pi.requestedPermissions.length; i++)
+ if (Manifest.permission.READ_CALENDAR.equals(pi.requestedPermissions[i]))
+ permissions.add(Manifest.permission.READ_CALENDAR);
+ else if (Manifest.permission.WRITE_CALENDAR.equals(pi.requestedPermissions[i]))
+ permissions.add(Manifest.permission.WRITE_CALENDAR);
+ } catch (Throwable ex) {
+ Log.w(ex);
+ }
+
return permissions.toArray(new String[0]);
}
diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java
index 2b1c067922..0a9a5b2604 100644
--- a/app/src/main/java/eu/faircode/email/MessageHelper.java
+++ b/app/src/main/java/eu/faircode/email/MessageHelper.java
@@ -21,10 +21,16 @@ 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;
@@ -139,7 +145,9 @@ import javax.mail.internet.ParseException;
import biweekly.Biweekly;
import biweekly.ICalendar;
+import biweekly.component.VEvent;
import biweekly.property.Method;
+import biweekly.util.ICalDate;
public class MessageHelper {
private boolean ensuredEnvelope = false;
@@ -3681,6 +3689,85 @@ public class MessageHelper {
db.attachment().setWarning(local.id, Log.formatThrowable(ex));
}
+ else if ("text/calendar".equals(local.type))
+ try {
+ EntityMessage message = db.message().getMessage(local.message);
+ EntityFolder folder = (message == null ? null : db.folder().getFolder(message.folder));
+ EntityAccount account = (folder == null ? null : db.account().getAccount(folder.account));
+ if (folder != null && EntityFolder.INBOX.equals(folder.type) &&
+ Helper.hasPermission(context, Manifest.permission.WRITE_CALENDAR)) {
+ ICalendar icalendar = Biweekly.parse(file).first();
+ VEvent event = icalendar.getEvents().get(0);
+
+ 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 uid = (event.getUid() == null ? null : event.getUid().getValue());
+
+ if (start != null && end != null) {
+ ContentResolver resolver = context.getContentResolver();
+
+ if (!TextUtils.isEmpty(uid))
+ 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);
+ }
+ }
+
+ try (Cursor cursor = resolver.query(CalendarContract.Calendars.CONTENT_URI,
+ new String[]{CalendarContract.Calendars._ID},
+ CalendarContract.Calendars.VISIBLE + " = 1 AND " +
+ CalendarContract.Calendars.IS_PRIMARY + " = 1 AND " +
+ CalendarContract.Calendars.ACCOUNT_NAME + " = ?",
+ new String[]{account.user},
+ null)) {
+ while (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 (!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);
+ }
+ }
+ }
+ }
+ } catch (Throwable ex) {
+ Log.w(ex);
+ db.attachment().setWarning(local.id, Log.formatThrowable(ex));
+ }
+
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && local.isCompressed()) {
// https://commons.apache.org/proper/commons-compress/examples.html
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);