From 8ae8898a890035bf6ad501e52102d68ed470c0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Fri, 27 Sep 2024 18:36:52 +0100 Subject: [PATCH] Sync birthdays with calendar events --- .../gadgetbridge/util/DateTimeUtils.java | 13 +++ .../util/calendar/CalendarManager.java | 97 ++++++++++++++++++- app/src/main/res/values/strings.xml | 4 + .../res/xml/devicesettings_sync_calendar.xml | 8 ++ 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java index 840e11652..ef9a69a54 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java @@ -25,6 +25,7 @@ import java.text.FieldPosition; import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; +import java.time.LocalDate; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -128,6 +129,18 @@ public class DateTimeUtils { return calendar.getTime(); } + public static Date dayStart(final LocalDate date) { + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, date.getYear()); + calendar.set(Calendar.MONTH, date.getMonthValue() - 1); + calendar.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth()); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + public static Date dayEnd(final Date date) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java index 20956a96f..7488ac348 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java @@ -23,19 +23,27 @@ import android.database.Cursor; import android.net.Uri; import android.provider.CalendarContract; import android.provider.CalendarContract.Instances; +import android.provider.ContactsContract; import android.text.format.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Calendar; +import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -82,7 +90,18 @@ public class CalendarManager { public List getCalendarEventList() { loadCalendarsBlackList(); - final List calendarEventList = new ArrayList(); + final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); + + final List calendarEventList = getCalendarEvents(); + if (sharedPrefs.getBoolean("sync_birthdays", false)) { + calendarEventList.addAll(getBirthdays()); + calendarEventList.sort(Comparator.comparingInt(CalendarEvent::getBeginSeconds)); + } + return calendarEventList; + } + + public List getCalendarEvents() { + final List calendarEventList = new ArrayList<>(); Calendar cal = GregorianCalendar.getInstance(); long dtStart = cal.getTimeInMillis(); @@ -160,6 +179,78 @@ public class CalendarManager { } } + public List getBirthdays() { + final String[] projection = new String[]{ + ContactsContract.CommonDataKinds.Event.CONTACT_ID, + ContactsContract.CommonDataKinds.Event.START_DATE, + ContactsContract.CommonDataKinds.Event.DISPLAY_NAME + }; + final String selection = ContactsContract.Data.MIMETYPE + " = ? AND " + + ContactsContract.CommonDataKinds.Event.TYPE + " = ?"; + final String[] selectionArgs = new String[]{ + ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, + String.valueOf(ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) + }; + final List birthdays = new LinkedList<>(); + final LocalDate maxDate = LocalDate.now().plusDays(30); + + try (Cursor birthdayCursor = mContext.getContentResolver().query(ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, ContactsContract.CommonDataKinds.Event.START_DATE + " ASC")) { + if (birthdayCursor == null || birthdayCursor.getCount() == 0) { + return birthdays; + } + while (birthdayCursor.moveToNext()) { + final String contactId = birthdayCursor.getString(birthdayCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.CONTACT_ID)); + final String birthdayStr = birthdayCursor.getString(birthdayCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.START_DATE)); + final String displayName = birthdayCursor.getString(birthdayCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.DISPLAY_NAME)); + final LocalDate birthday = parseBirthday(birthdayStr); + if (birthday == null || birthday.isAfter(maxDate)) { + continue; + } + + birthdays.add(new CalendarEvent( + DateTimeUtils.dayStart(birthday).getTime(), + DateTimeUtils.dayStart(birthday).getTime() + 86400000L - 1L, + contactId.hashCode(), + mContext.getString(R.string.contact_birthday, displayName), + null, + null, + mContext.getString(R.string.birthdays), + mContext.getString(R.string.pref_contacts_title), + 0, + true, + null + )); + } + } catch (final Exception e) { + LOG.error("could not query birthdays, permission denied?", e); + } + return birthdays; + } + + private LocalDate parseBirthday(final String birthdayStr) { + final LocalDate birthday; + final LocalDate now = LocalDate.now(); + + try { + if (birthdayStr.startsWith("--")) { + // MM-DD + final String monthDay = birthdayStr.substring(2); + birthday = LocalDate.parse(now.getYear() + "-" + monthDay, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } else { + birthday = LocalDate.parse(birthdayStr, DateTimeFormatter.ISO_LOCAL_DATE).withYear(now.getYear()); + } + } catch (final DateTimeParseException e) { + LOG.error("Failed to parse birthday {}", birthdayStr, e); + return null; + } + + if (birthday.isAfter(now)) { + return birthday; + } + + return birthday.plusYears(1); + } + private static HashSet calendars_blacklist = null; public boolean calendarIsBlacklisted(String calendarUniqueName) { @@ -182,7 +273,7 @@ public class CalendarManager { public void addCalendarToBlacklist(String calendarUniqueName) { if (calendars_blacklist.add(calendarUniqueName)) { - LOG.info("Blacklisted calendar " + calendarUniqueName); + LOG.info("Blacklisted calendar {}", calendarUniqueName); saveCalendarsBlackList(); } else { LOG.warn("Calendar {} already blacklisted!", calendarUniqueName); @@ -191,7 +282,7 @@ public class CalendarManager { public void removeFromCalendarBlacklist(String calendarUniqueName) { calendars_blacklist.remove(calendarUniqueName); - LOG.info("Unblacklisted calendar " + calendarUniqueName); + LOG.info("Unblacklisted calendar {}", calendarUniqueName); saveCalendarsBlackList(); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe964546c..0d9b7be65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -422,6 +422,8 @@ Allow Bangle.js watch apps to send Android Intents, and allow other apps on Android (like Tasker) to send data to Bangle.js with the com.banglejs.uart.tx Intent. Needs permission to display over other apps to work in the background. Enables calendar alerts, even when disconnected Sync calendar events + Sync contact birthdays alongside calendar events + Sync birthdays Relax firmware checks Enable this if you want to flash a firmware not intended for your device (at your own risk) Vibration strength @@ -3317,4 +3319,6 @@ Trip: %.1f mi Total: %.1f mi no cycling sensor found + %1s\'s birthday + Birthdays diff --git a/app/src/main/res/xml/devicesettings_sync_calendar.xml b/app/src/main/res/xml/devicesettings_sync_calendar.xml index aa3efb172..70388ba1b 100644 --- a/app/src/main/res/xml/devicesettings_sync_calendar.xml +++ b/app/src/main/res/xml/devicesettings_sync_calendar.xml @@ -7,6 +7,14 @@ android:layout="@layout/preference_checkbox" android:summary="@string/pref_summary_sync_calendar" android:title="@string/pref_title_sync_caldendar" /> +