diff --git a/app/src/main/java/com/etesync/syncadapter/NotificationHelper.java b/app/src/main/java/com/etesync/syncadapter/NotificationHelper.java index 2f62980e..3b423170 100644 --- a/app/src/main/java/com/etesync/syncadapter/NotificationHelper.java +++ b/app/src/main/java/com/etesync/syncadapter/NotificationHelper.java @@ -82,17 +82,22 @@ public class NotificationHelper { } public void notify(String title, String content, String bigText, Intent intent) { + notify(title, content, bigText, intent, -1); + } + + public void notify(String title, String content, String bigText, Intent intent, int icon) { createNotificationChannel(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - int icon; - String category; - //Check if error was configured - if (throwable == null) { - icon = R.drawable.ic_sync_dark; - category = NotificationCompat.CATEGORY_STATUS; - } else { - icon = R.drawable.ic_error_light; - category = NotificationCompat.CATEGORY_ERROR; + String category = + throwable == null ? + NotificationCompat.CATEGORY_STATUS : NotificationCompat.CATEGORY_ERROR; + if (icon == -1) { + //Check if error was configured + if (throwable == null) { + icon = R.drawable.ic_sync_dark; + } else { + icon = R.drawable.ic_error_light; + } } builder.setLargeIcon(App.getLauncherBitmap(context)) diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java index 65f47615..b1467bbc 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.java @@ -10,12 +10,16 @@ package com.etesync.syncadapter.syncadapter; import android.accounts.Account; import android.content.Context; +import android.content.Intent; import android.content.SyncResult; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import com.etesync.syncadapter.AccountSettings; import com.etesync.syncadapter.App; import com.etesync.syncadapter.Constants; +import com.etesync.syncadapter.NotificationHelper; import com.etesync.syncadapter.R; import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.JournalEntryManager; @@ -25,11 +29,23 @@ import com.etesync.syncadapter.resource.LocalCalendar; import com.etesync.syncadapter.resource.LocalEvent; import com.etesync.syncadapter.resource.LocalResource; +import net.fortuna.ical4j.model.property.Attendee; + +import org.acra.attachment.AcraContentProvider; +import org.acra.util.IOUtils; import org.apache.commons.codec.Charsets; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; import at.bitfire.ical4android.CalendarStorageException; import at.bitfire.ical4android.Event; @@ -61,8 +77,8 @@ public class CalendarSyncManager extends SyncManager { @Override protected String getSyncSuccessfullyTitle() { - return context.getString(R.string.sync_successfully_calendar, info.displayName, - account.name); + return context.getString(R.string.sync_successfully_calendar, info.displayName, + account.name); } @Override @@ -95,8 +111,9 @@ public class CalendarSyncManager extends SyncManager { if (events.length == 0) { App.log.warning("Received VCard without data, ignoring"); return; - } else if (events.length > 1) + } else if (events.length > 1) { App.log.warning("Received multiple VCALs, using first one"); + } Event event = events[0]; LocalEvent local = (LocalEvent) localCollection.getByUid(event.uid); @@ -113,6 +130,57 @@ public class CalendarSyncManager extends SyncManager { } } + protected void createLocalEntries() throws CalendarStorageException, ContactsStorageException, IOException { + super.createLocalEntries(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + createInviteAttendeesNotification(); + } + } + + private void createInviteAttendeesNotification() throws CalendarStorageException, ContactsStorageException, IOException { + for (LocalResource local : localDirty) { + Event event = ((LocalEvent) local).getEvent(); + + if (event.attendees.isEmpty()) { + return; + } + createInviteAttendeesNotification(event, local.getContent()); + } + } + + private void createInviteAttendeesNotification(Event event, String icsContent) { + NotificationHelper notificationHelper = new NotificationHelper(context, event.uid, event.uid.hashCode()); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_EMAIL, getEmailAddresses(event.attendees ,false)); + final DateFormat dateFormatDate = + new SimpleDateFormat("EEEE, MMM dd", Locale.US); + intent.putExtra(Intent.EXTRA_SUBJECT, + context.getString(R.string.sync_calendar_attendees_email_subject, + event.summary, + dateFormatDate.format(event.dtStart.getDate()))); + intent.putExtra(Intent.EXTRA_TEXT, + context.getString(R.string.sync_calendar_attendees_email_content, + event.summary, + formatEventDates(event), + formatAttendees(event.attendees))); + Uri uri = createAttachmentFromString(context, event.uid, icsContent); + if (uri == null) { + App.log.severe("Unable to create attachment from calendar event"); + return; + } + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra(Intent.EXTRA_STREAM, uri); + notificationHelper.notify( + context.getString( + R.string.sync_calendar_attendees_notification_title, event.summary), + context.getString(R.string.sync_calendar_attendees_notification_content), + null, + intent, + R.drawable.ic_email_black); + } + private LocalResource processEvent(final Event newData, LocalEvent localEvent) throws IOException, ContactsStorageException, CalendarStorageException { // delete local event, if it exists if (localEvent != null) { @@ -129,4 +197,63 @@ public class CalendarSyncManager extends SyncManager { return localEvent; } + + private String[] getEmailAddresses(List attendees, + boolean shouldIncludeAccount) { + List attendeesEmails = new ArrayList<>(attendees.size()); + for (Attendee attendee : attendees) { + String attendeeEmail = attendee.getValue().replace("mailto:", ""); + if (!shouldIncludeAccount && attendeeEmail.equals(account.name)) { + continue; + } + attendeesEmails.add(attendeeEmail); + } + return attendeesEmails.toArray(new String[0]); + } + + private String formatAttendees(List attendeesList) { + StringBuilder stringBuilder = new StringBuilder(); + String[] attendees = getEmailAddresses(attendeesList, true); + for (String attendee : attendees) { + stringBuilder.append("\n ").append(attendee); + } + return stringBuilder.toString(); + } + + private static String formatEventDates(Event event) { + final String dateFormatString = + event.isAllDay() ? "EEEE, MMM dd" : "EEEE, MMM dd @ hh:mm a"; + final DateFormat dateFormat = + new SimpleDateFormat(dateFormatString, + Locale.US); + Date startDate = event.dtStart.getDate(); + Date endDate = event.dtEnd.getDate(); + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + cal1.setTime(startDate); + cal2.setTime(endDate); + boolean sameDay = cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR); + if (sameDay && event.isAllDay()) { + return dateFormat.format(startDate); + } + return sameDay ? + String.format("%s - %s", + dateFormat.format(startDate), + new SimpleDateFormat("hh:mm a", Locale.US).format(endDate)) : + String.format("%s - %s", dateFormat.format(startDate), dateFormat.format(endDate)); + } + + private Uri createAttachmentFromString(Context context, String name, String content) { + final File parentDir = new File (context.getCacheDir(), name); + parentDir.mkdirs(); + final File cache = new File(parentDir, "invite.ics"); + try { + IOUtils.writeStringToFile(cache, content); + return AcraContentProvider.getUriForFile(context, cache); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } } diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java index 70299ce1..d9647476 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.java @@ -35,8 +35,6 @@ import com.etesync.syncadapter.resource.LocalResource; import com.etesync.syncadapter.ui.DebugInfoActivity; import com.etesync.syncadapter.ui.ViewCollectionActivity; -import org.apache.commons.collections4.ListUtils; - import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; @@ -101,7 +99,7 @@ abstract public class SyncManager { * Dirty and deleted resources. We need to save them so we safely ignore ones that were added after we started. */ private List localDeleted; - private LocalResource[] localDirty; + protected LocalResource[] localDirty; public SyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult syncResult, String journalUid, CollectionInfo.Type serviceType, String accountName) throws Exceptions.IntegrityException, Exceptions.GenericCryptoException { this.context = context; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 000e0166..e895b008 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -322,6 +322,10 @@ Contacts modified (%s) %s modified. %s added.\n%s updated.\n%s deleted. + %s + Send invitations to guests? + Invitation: %s @ %s + You have been invited to the following event:\n\n%s\nWhen: %s\nAttendees: %s EteSync: Connection security