diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index f32c441dc..78ff2326d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -117,7 +117,7 @@ public class GBApplication extends Application { private static SharedPreferences sharedPrefs; private static final String PREFS_VERSION = "shared_preferences_version"; //if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version - private static final int CURRENT_PREFS_VERSION = 16; + private static final int CURRENT_PREFS_VERSION = 17; private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); private static Prefs prefs; @@ -224,7 +224,6 @@ public class GBApplication extends Application { deviceService = createDeviceService(); loadAppsNotifBlackList(); loadAppsPebbleBlackList(); - loadCalendarsBlackList(); PeriodicExporter.enablePeriodicExport(context); @@ -557,61 +556,6 @@ public class GBApplication extends Application { return packageName; } - private static HashSet calendars_blacklist = null; - - public static boolean calendarIsBlacklisted(String calendarUniqueName) { - if (calendars_blacklist == null) { - GB.log("calendarIsBlacklisted: calendars_blacklist is null!", GB.INFO, null); - } - return calendars_blacklist != null && calendars_blacklist.contains(calendarUniqueName); - } - - public static void setCalendarsBlackList(Set calendarNames) { - if (calendarNames == null) { - GB.log("Set null apps_notification_blacklist", GB.INFO, null); - calendars_blacklist = new HashSet<>(); - } else { - calendars_blacklist = new HashSet<>(calendarNames); - } - GB.log("New calendars_blacklist has " + calendars_blacklist.size() + " entries", GB.INFO, null); - saveCalendarsBlackList(); - } - - public static void addCalendarToBlacklist(String calendarUniqueName) { - if (calendars_blacklist.add(calendarUniqueName)) { - GB.log("Blacklisted calendar " + calendarUniqueName, GB.INFO, null); - saveCalendarsBlackList(); - } else { - GB.log("Calendar " + calendarUniqueName + " already blacklisted!", GB.WARN, null); - } - } - - public static void removeFromCalendarBlacklist(String calendarUniqueName) { - calendars_blacklist.remove(calendarUniqueName); - GB.log("Unblacklisted calendar " + calendarUniqueName, GB.INFO, null); - saveCalendarsBlackList(); - } - - private static void loadCalendarsBlackList() { - GB.log("Loading calendars_blacklist", GB.INFO, null); - calendars_blacklist = (HashSet) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null); // lgtm [java/abstract-to-concrete-cast] - if (calendars_blacklist == null) { - calendars_blacklist = new HashSet<>(); - } - GB.log("Loaded calendars_blacklist has " + calendars_blacklist.size() + " entries", GB.INFO, null); - } - - private static void saveCalendarsBlackList() { - GB.log("Saving calendars_blacklist with " + calendars_blacklist.size() + " entries", GB.INFO, null); - SharedPreferences.Editor editor = sharedPrefs.edit(); - if (calendars_blacklist.isEmpty()) { - editor.putStringSet(GBPrefs.CALENDAR_BLACKLIST, null); - } else { - Prefs.putStringSet(editor, GBPrefs.CALENDAR_BLACKLIST, calendars_blacklist); - } - editor.apply(); - } - /** * Deletes both the old Activity database and the new one recreates it with empty tables. * @@ -1190,6 +1134,32 @@ public class GBApplication extends Application { } } + if (oldVersion < 17) { + final HashSet calendarBlacklist = (HashSet) prefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null); + + try (DBHandler db = acquireDB()) { + final DaoSession daoSession = db.getDaoSession(); + final List activeDevices = DBHelper.getActiveDevices(daoSession); + + for (Device dbDevice : activeDevices) { + final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()); + final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit(); + + deviceSharedPrefsEdit.putBoolean("sync_calendar", prefs.getBoolean("enable_calendar_sync", true)); + + if (calendarBlacklist != null) { + Prefs.putStringSet(deviceSharedPrefsEdit, GBPrefs.CALENDAR_BLACKLIST, calendarBlacklist); + } + + deviceSharedPrefsEdit.apply(); + } + } catch (Exception e) { + Log.w(TAG, "error acquiring DB lock"); + } + + editor.remove(GBPrefs.CALENDAR_BLACKLIST); + } + editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION)); editor.apply(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java index cfdf1afbf..792e868ce 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java @@ -44,7 +44,9 @@ import androidx.core.app.ActivityCompat; import androidx.core.app.NavUtils; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager; public class CalBlacklistActivity extends AbstractGBActivity { @@ -56,12 +58,18 @@ public class CalBlacklistActivity extends AbstractGBActivity { }; private ArrayList calendarsArrayList; + private GBDevice gbDevice; + private CalendarManager calendarManager; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calblacklist); ListView calListView = (ListView) findViewById(R.id.calListView); + gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE); + calendarManager = new CalendarManager(this, gbDevice.getAddress()); + final Uri uri = CalendarContract.Calendars.CONTENT_URI; if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { GB.toast(this, "Calendar permission not granted. Nothing to do.", Toast.LENGTH_SHORT, GB.WARN); @@ -83,9 +91,9 @@ public class CalBlacklistActivity extends AbstractGBActivity { CheckBox selected = (CheckBox) view.findViewById(R.id.item_checkbox); toggleEntry(view); if (selected.isChecked()) { - GBApplication.addCalendarToBlacklist(item.getUniqueString()); + calendarManager.addCalendarToBlacklist(item.getUniqueString()); } else { - GBApplication.removeFromCalendarBlacklist(item.getUniqueString()); + calendarManager.removeFromCalendarBlacklist(item.getUniqueString()); } } }); @@ -148,8 +156,8 @@ public class CalBlacklistActivity extends AbstractGBActivity { TextView ownerAccount = (TextView) view.findViewById(R.id.calendar_owner_account); CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox); - if (GBApplication.calendarIsBlacklisted(item.getUniqueString()) && !checked.isChecked() || - !GBApplication.calendarIsBlacklisted(item.getUniqueString()) && checked.isChecked()) { + if (calendarManager.calendarIsBlacklisted(item.getUniqueString()) && !checked.isChecked() || + !calendarManager.calendarIsBlacklisted(item.getUniqueString()) && checked.isChecked()) { toggleEntry(view); } color.setBackgroundColor(item.color); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index 5970cc691..35cb2e18a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -138,15 +138,6 @@ public class SettingsActivity extends AbstractSettingsActivity { } }); - pref = findPreference("pref_key_blacklist_calendars"); - pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - Intent enableIntent = new Intent(SettingsActivity.this, CalBlacklistActivity.class); - startActivity(enableIntent); - return true; - } - }); - pref = findPreference("pebble_emu_addr"); pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index 7490be798..d1f6060b9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -49,7 +49,9 @@ import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks; +import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst; @@ -688,6 +690,18 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp }); } + final Preference calendarBlacklist = findPreference("blacklist_calendars"); + if (calendarBlacklist != null) { + calendarBlacklist.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(getContext(), CalBlacklistActivity.class); + intent.putExtra(GBDevice.EXTRA_DEVICE, device); + startActivity(intent); + return true; + } + }); + } + final Preference cannedMessagesDismissCall = findPreference("canned_messages_dismisscall_send"); if (cannedMessagesDismissCall != null) { cannedMessagesDismissCall.setOnPreferenceClickListener(new androidx.preference.Preference.OnPreferenceClickListener() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 499f3b7fd..377d42283 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -230,6 +230,7 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator { R.xml.devicesettings_autoremove_notifications, R.xml.devicesettings_canned_reply_16, R.xml.devicesettings_canned_dismisscall_16, + R.xml.devicesettings_sync_calendar, R.xml.devicesettings_transliteration }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java index 5bdc10f6d..c3ea7cb8e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java @@ -41,7 +41,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; -import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager; import nodomain.freeyourgadget.gadgetbridge.util.GB; public class CalendarReceiver extends BroadcastReceiver { @@ -52,9 +53,9 @@ public class CalendarReceiver extends BroadcastReceiver { private class EventSyncState { private int state; - private CalendarEvents.CalendarEvent event; + private CalendarEvent event; - EventSyncState(CalendarEvents.CalendarEvent event, int state) { + EventSyncState(CalendarEvent event, int state) { this.state = state; this.event = event; } @@ -67,11 +68,11 @@ public class CalendarReceiver extends BroadcastReceiver { this.state = state; } - public CalendarEvents.CalendarEvent getEvent() { + public CalendarEvent getEvent() { return event; } - public void setEvent(CalendarEvents.CalendarEvent event) { + public void setEvent(CalendarEvent event) { this.event = event; } } @@ -92,11 +93,11 @@ public class CalendarReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { LOG.info("got calendar changed broadcast"); - List eventList = (new CalendarEvents()).getCalendarEventList(GBApplication.getContext()); + List eventList = (new CalendarManager(context, mGBDevice.getAddress())).getCalendarEventList(); syncCalendar(eventList); } - public void syncCalendar(List eventList) { + public void syncCalendar(List eventList) { try (DBHandler dbHandler = GBApplication.acquireDB()) { DaoSession session = dbHandler.getDaoSession(); syncCalendar(eventList, session); @@ -105,14 +106,14 @@ public class CalendarReceiver extends BroadcastReceiver { } } - public void syncCalendar(List eventList, DaoSession session) { + public void syncCalendar(List eventList, DaoSession session) { LOG.info("Syncing with calendar."); - Hashtable eventTable = new Hashtable<>(); + Hashtable eventTable = new Hashtable<>(); Long deviceId = DBHelper.getDevice(mGBDevice, session).getId(); QueryBuilder qb = session.getCalendarSyncStateDao().queryBuilder(); - for (CalendarEvents.CalendarEvent e : eventList) { + for (CalendarEvent e : eventList) { long id = e.getId(); eventTable.put(id, e); if (!eventState.containsKey(e.getId())) { @@ -176,7 +177,7 @@ public class CalendarReceiver extends BroadcastReceiver { EventSyncState es = eventState.get(i); int syncState = es.getState(); if (syncState == EventState.NOT_SYNCED || syncState == EventState.NEEDS_UPDATE) { - CalendarEvents.CalendarEvent calendarEvent = es.getEvent(); + CalendarEvent calendarEvent = es.getEvent(); CalendarEventSpec calendarEventSpec = new CalendarEventSpec(); calendarEventSpec.id = i; calendarEventSpec.title = calendarEvent.getTitle(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java deleted file mode 100644 index 776ac299b..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java +++ /dev/null @@ -1,244 +0,0 @@ -/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele - Gobbetti, Daniel Hauck - - This file is part of Gadgetbridge. - - Gadgetbridge is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Gadgetbridge 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.model; - -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Instances; -import android.text.format.Time; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Objects; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; - -public class CalendarEvents { - private static final Logger LOG = LoggerFactory.getLogger(CalendarEvents.class); - - // needed for pebble: time, duration, layout, reminders, actions - // layout: type, title, subtitle, body (max 512), tinyIcon, smallIcon, largeIcon - //further: primaryColor, secondaryColor, backgroundColor, headings, paragraphs, lastUpdated - // taken from: https://developer.getpebble.com/guides/timeline/pin-structure/ - - // needed for MiBand: - // time - - private static final String[] EVENT_INSTANCE_PROJECTION = new String[]{ - Instances._ID, - - Instances.BEGIN, - Instances.END, - Instances.DURATION, - Instances.TITLE, - Instances.DESCRIPTION, - Instances.EVENT_LOCATION, - Instances.CALENDAR_DISPLAY_NAME, - CalendarContract.Calendars.ACCOUNT_NAME, - Instances.CALENDAR_COLOR, - Instances.ALL_DAY - }; - - private static final int lookahead_days = 7; - - private List calendarEventList = new ArrayList(); - - public List getCalendarEventList(Context mContext) { - fetchSystemEvents(mContext); - return calendarEventList; - } - - private boolean fetchSystemEvents(Context mContext) { - - Calendar cal = GregorianCalendar.getInstance(); - long dtStart = cal.getTimeInMillis(); - cal.add(Calendar.DATE, lookahead_days); - long dtEnd = cal.getTimeInMillis(); - - Uri.Builder eventsUriBuilder = Instances.CONTENT_URI.buildUpon(); - ContentUris.appendId(eventsUriBuilder, dtStart); - ContentUris.appendId(eventsUriBuilder, dtEnd); - Uri eventsUri = eventsUriBuilder.build(); - - try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, Instances.BEGIN + " ASC")) { - if (evtCursor == null || evtCursor.getCount() == 0) { - return false; - } - while (evtCursor.moveToNext()) { - long start = evtCursor.getLong(1); - long end = evtCursor.getLong(2); - if (end == 0) { - LOG.info("no end time, will parse duration string"); - Time time = new Time(); //FIXME: deprecated FTW - time.parse(evtCursor.getString(3)); - end = start + time.toMillis(false); - } - CalendarEvent calEvent = new CalendarEvent( - start, - end, - evtCursor.getLong(0), - evtCursor.getString(4), - evtCursor.getString(5), - evtCursor.getString(6), - evtCursor.getString(7), - evtCursor.getString(8), - evtCursor.getInt(9), - !evtCursor.getString(10).equals("0") - ); - if (!GBApplication.calendarIsBlacklisted(calEvent.getUniqueCalName())) { - calendarEventList.add(calEvent); - } else { - LOG.debug("calendar " + calEvent.getUniqueCalName() + " skipped because it's blacklisted"); - } - } - return true; - } catch (Exception e) { - LOG.error("could not query calendar, permission denied?"); - return false; - } - } - - public static class CalendarEvent { - private long begin; - private long end; - private long id; - private String title; - private String description; - private String location; - private String calName; - private String calAccountName; - private int color; - private boolean allDay; - - public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, String calAccountName, int color, boolean allDay) { - this.begin = begin; - this.end = end; - this.id = id; - this.title = title; - this.description = description; - this.location = location; - this.calName = calName; - this.calAccountName = calAccountName; - this.color = color; - this.allDay = allDay; - } - - public long getBegin() { - return begin; - } - - public int getBeginSeconds() { - return (int) (begin / 1000); - } - - public long getEnd() { - return end; - } - - public long getDuration() { - return end - begin; - } - - public int getDurationSeconds() { - return (int) ((getDuration()) / 1000); - } - - public short getDurationMinutes() { - return (short) (getDurationSeconds() / 60); - } - - - public long getId() { - return id; - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - public String getLocation() { - return location; - } - - public String getCalName() { - return calName; - } - - public String getCalAccountName() { - return calAccountName; - } - - public String getUniqueCalName() { - return getCalAccountName() + '/' + getCalName(); - } - - public int getColor() { - return color; - } - - public boolean isAllDay() { - return allDay; - } - - @Override - public boolean equals(Object other) { - if (other instanceof CalendarEvent) { - CalendarEvent e = (CalendarEvent) other; - return (this.getId() == e.getId()) && - Objects.equals(this.getTitle(), e.getTitle()) && - (this.getBegin() == e.getBegin()) && - Objects.equals(this.getLocation(), e.getLocation()) && - Objects.equals(this.getDescription(), e.getDescription()) && - (this.getEnd() == e.getEnd()) && - Objects.equals(this.getCalName(), e.getCalName()) && - Objects.equals(this.getCalAccountName(), e.getCalAccountName()) && - (this.getColor() == e.getColor()) && - (this.isAllDay() == e.isAllDay()); - } else { - return false; - } - } - - @Override - public int hashCode() { - int result = (int) id; - result = 31 * result + Objects.hash(title); - result = 31 * result + Long.valueOf(begin).hashCode(); - result = 31 * result + Objects.hash(location); - result = 31 * result + Objects.hash(description); - result = 31 * result + Long.valueOf(end).hashCode(); - result = 31 * result + Objects.hash(calName); - result = 31 * result + Objects.hash(calAccountName); - result = 31 * result + Integer.valueOf(color).hashCode(); - result = 31 * result + Boolean.valueOf(allDay).hashCode(); - return result; - } - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index bd116730b..b28aba862 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -976,7 +976,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere } if (enable && initialized && features.supportsCalendarEvents()) { - if (mCalendarReceiver == null && getPrefs().getBoolean("enable_calendar_sync", true)) { + if (mCalendarReceiver == null) { if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) { IntentFilter calendarIntentFilter = new IntentFilter(); calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index 52a87bbd0..bbd8fb20f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -110,7 +110,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; -import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; @@ -2509,12 +2510,12 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport { int availableSlots = prefs.getInt(PREF_RESERVER_ALARMS_CALENDAR, 0); if (availableSlots > 0) { - CalendarEvents upcomingEvents = new CalendarEvents(); - List mEvents = upcomingEvents.getCalendarEventList(getContext()); + CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress()); + List mEvents = upcomingEvents.getCalendarEventList(); int iteration = 0; - for (CalendarEvents.CalendarEvent mEvt : mEvents) { + for (CalendarEvent mEvt : mEvents) { if (mEvt.isAllDay()) { continue; } @@ -2541,13 +2542,13 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport { final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); int availableSlots = prefs.getInt(PREF_RESERVER_REMINDERS_CALENDAR, 9); - CalendarEvents upcomingEvents = new CalendarEvents(); - List calendarEvents = upcomingEvents.getCalendarEventList(getContext()); + CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress()); + List calendarEvents = upcomingEvents.getCalendarEventList(); Calendar calendar = Calendar.getInstance(); int iteration = 0; - for (CalendarEvents.CalendarEvent calendarEvent : calendarEvents) { + for (CalendarEvent calendarEvent : calendarEvents) { if (calendarEvent.isAllDay()) { continue; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 97dbb6140..e408d735d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -65,7 +65,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; -import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; @@ -1233,11 +1234,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { availableSlots = 3; } if (availableSlots > 0) { - CalendarEvents upcomingEvents = new CalendarEvents(); - List mEvents = upcomingEvents.getCalendarEventList(getContext()); + CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress()); + List mEvents = upcomingEvents.getCalendarEventList(); int iteration = 0; - for (CalendarEvents.CalendarEvent mEvt : mEvents) { + for (CalendarEvent mEvt : mEvents) { if (iteration >= availableSlots) { break; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java index e47cd416f..7259d2e5e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java @@ -56,7 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; -import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; @@ -73,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR; @@ -594,11 +595,11 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport { return; } - CalendarEvents upcomingEvents = new CalendarEvents(); - List calendarEvents = upcomingEvents.getCalendarEventList(getContext()); + CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress()); + List calendarEvents = upcomingEvents.getCalendarEventList(); int eventCount = 0; - for (CalendarEvents.CalendarEvent calendarEvent : calendarEvents) { + for (CalendarEvent calendarEvent : calendarEvents) { if (calendarEvent.isAllDay()) { continue; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java index bc8730567..406945ef1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java @@ -134,14 +134,6 @@ public class ImportExportSharedPreferences { } GBApplication.setAppsPebbleBlackList(apps_pebble_blacklist); break; - case GBPrefs.CALENDAR_BLACKLIST: //TODO: untested - Set calendars_blacklist = new HashSet<>(); - text = text.replace("[", "").replace("]", ""); - for (int z = 0; z < text.split(",").length; z++) { - calendars_blacklist.add(text.split(",")[z].trim()); - } - GBApplication.setCalendarsBlackList(calendars_blacklist); - break; } } else if (!PREFERENCES.equals(name)) { throw new Exception("Unknown type " + name); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java new file mode 100644 index 000000000..bf78fb506 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java @@ -0,0 +1,141 @@ +/* Copyright (C) 2017-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, Daniel Hauck + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.util.calendar; + +import java.util.Objects; + +public class CalendarEvent { + private long begin; + private long end; + private long id; + private String title; + private String description; + private String location; + private String calName; + private String calAccountName; + private int color; + private boolean allDay; + + public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, String calAccountName, int color, boolean allDay) { + this.begin = begin; + this.end = end; + this.id = id; + this.title = title; + this.description = description; + this.location = location; + this.calName = calName; + this.calAccountName = calAccountName; + this.color = color; + this.allDay = allDay; + } + + public long getBegin() { + return begin; + } + + public int getBeginSeconds() { + return (int) (begin / 1000); + } + + public long getEnd() { + return end; + } + + public long getDuration() { + return end - begin; + } + + public int getDurationSeconds() { + return (int) ((getDuration()) / 1000); + } + + public short getDurationMinutes() { + return (short) (getDurationSeconds() / 60); + } + + + public long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getLocation() { + return location; + } + + public String getCalName() { + return calName; + } + + public String getCalAccountName() { + return calAccountName; + } + + public String getUniqueCalName() { + return getCalAccountName() + '/' + getCalName(); + } + + public int getColor() { + return color; + } + + public boolean isAllDay() { + return allDay; + } + + @Override + public boolean equals(Object other) { + if (other instanceof CalendarEvent) { + CalendarEvent e = (CalendarEvent) other; + return (this.getId() == e.getId()) && + Objects.equals(this.getTitle(), e.getTitle()) && + (this.getBegin() == e.getBegin()) && + Objects.equals(this.getLocation(), e.getLocation()) && + Objects.equals(this.getDescription(), e.getDescription()) && + (this.getEnd() == e.getEnd()) && + Objects.equals(this.getCalName(), e.getCalName()) && + Objects.equals(this.getCalAccountName(), e.getCalAccountName()) && + (this.getColor() == e.getColor()) && + (this.isAllDay() == e.isAllDay()); + } else { + return false; + } + } + + @Override + public int hashCode() { + int result = (int) id; + result = 31 * result + Objects.hash(title); + result = 31 * result + Long.valueOf(begin).hashCode(); + result = 31 * result + Objects.hash(location); + result = 31 * result + Objects.hash(description); + result = 31 * result + Long.valueOf(end).hashCode(); + result = 31 * result + Objects.hash(calName); + result = 31 * result + Objects.hash(calAccountName); + result = 31 * result + Integer.valueOf(color).hashCode(); + result = 31 * result + Boolean.valueOf(allDay).hashCode(); + return result; + } +} 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 new file mode 100644 index 000000000..3db11c6ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java @@ -0,0 +1,193 @@ +/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, Daniel Hauck + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.util.calendar; + +import android.content.ContentUris; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.provider.CalendarContract; +import android.provider.CalendarContract.Instances; +import android.text.format.Time; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class CalendarManager { + private static final Logger LOG = LoggerFactory.getLogger(CalendarManager.class); + + // needed for pebble: time, duration, layout, reminders, actions + // layout: type, title, subtitle, body (max 512), tinyIcon, smallIcon, largeIcon + //further: primaryColor, secondaryColor, backgroundColor, headings, paragraphs, lastUpdated + // taken from: https://developer.getpebble.com/guides/timeline/pin-structure/ + + // needed for MiBand: + // time + + private static final String[] EVENT_INSTANCE_PROJECTION = new String[]{ + Instances._ID, + + Instances.BEGIN, + Instances.END, + Instances.DURATION, + Instances.TITLE, + Instances.DESCRIPTION, + Instances.EVENT_LOCATION, + Instances.CALENDAR_DISPLAY_NAME, + CalendarContract.Calendars.ACCOUNT_NAME, + Instances.CALENDAR_COLOR, + Instances.ALL_DAY + }; + + private static final int lookahead_days = 7; + + private final String deviceAddress; + private final Context mContext; + + public CalendarManager(final Context context, final String deviceAddress) { + this.mContext = context; + this.deviceAddress = deviceAddress; + + loadCalendarsBlackList(); + } + + public List getCalendarEventList() { + loadCalendarsBlackList(); + + final List calendarEventList = new ArrayList(); + + Calendar cal = GregorianCalendar.getInstance(); + long dtStart = cal.getTimeInMillis(); + cal.add(Calendar.DATE, lookahead_days); + long dtEnd = cal.getTimeInMillis(); + + Uri.Builder eventsUriBuilder = Instances.CONTENT_URI.buildUpon(); + ContentUris.appendId(eventsUriBuilder, dtStart); + ContentUris.appendId(eventsUriBuilder, dtEnd); + Uri eventsUri = eventsUriBuilder.build(); + + try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, Instances.BEGIN + " ASC")) { + if (evtCursor == null || evtCursor.getCount() == 0) { + return calendarEventList; + } + while (evtCursor.moveToNext()) { + long start = evtCursor.getLong(1); + long end = evtCursor.getLong(2); + if (end == 0) { + LOG.info("no end time, will parse duration string"); + Time time = new Time(); //FIXME: deprecated FTW + time.parse(evtCursor.getString(3)); + end = start + time.toMillis(false); + } + CalendarEvent calEvent = new CalendarEvent( + start, + end, + evtCursor.getLong(0), + evtCursor.getString(4), + evtCursor.getString(5), + evtCursor.getString(6), + evtCursor.getString(7), + evtCursor.getString(8), + evtCursor.getInt(9), + !evtCursor.getString(10).equals("0") + ); + if (!calendarIsBlacklisted(calEvent.getUniqueCalName())) { + calendarEventList.add(calEvent); + } else { + LOG.debug("calendar {} skipped because it's blacklisted", calEvent.getUniqueCalName()); + } + } + return calendarEventList; + } catch (final Exception e) { + LOG.error("could not query calendar, permission denied?", e); + return calendarEventList; + } + } + + private static HashSet calendars_blacklist = null; + + public boolean calendarIsBlacklisted(String calendarUniqueName) { + if (calendars_blacklist == null) { + LOG.warn("calendarIsBlacklisted: calendars_blacklist is null!"); + } + return calendars_blacklist != null && calendars_blacklist.contains(calendarUniqueName); + } + + public void setCalendarsBlackList(Set calendarNames) { + if (calendarNames == null) { + LOG.info("Set null apps_notification_blacklist"); + calendars_blacklist = new HashSet<>(); + } else { + calendars_blacklist = new HashSet<>(calendarNames); + } + LOG.info("New calendars_blacklist has {} entries", calendars_blacklist.size()); + saveCalendarsBlackList(); + } + + public void addCalendarToBlacklist(String calendarUniqueName) { + if (calendars_blacklist.add(calendarUniqueName)) { + LOG.info("Blacklisted calendar " + calendarUniqueName); + saveCalendarsBlackList(); + } else { + LOG.warn("Calendar {} already blacklisted!", calendarUniqueName); + } + } + + public void removeFromCalendarBlacklist(String calendarUniqueName) { + calendars_blacklist.remove(calendarUniqueName); + LOG.info("Unblacklisted calendar " + calendarUniqueName); + saveCalendarsBlackList(); + } + + private void loadCalendarsBlackList() { + SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); + + LOG.info("Loading calendars_blacklist"); + calendars_blacklist = (HashSet) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null); + if (calendars_blacklist == null) { + calendars_blacklist = new HashSet<>(); + } + LOG.info("Loaded calendars_blacklist has {} entries", calendars_blacklist.size()); + } + + private void saveCalendarsBlackList() { + final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); + + LOG.info("Saving calendars_blacklist with {} entries", calendars_blacklist.size()); + SharedPreferences.Editor editor = sharedPrefs.edit(); + if (calendars_blacklist.isEmpty()) { + editor.putStringSet(GBPrefs.CALENDAR_BLACKLIST, null); + } else { + Prefs.putStringSet(editor, GBPrefs.CALENDAR_BLACKLIST, calendars_blacklist); + } + editor.apply(); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e833805d9..d2c28eaff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -205,6 +205,7 @@ Used for the LineageOS weather provider, other Android versions need to use an app like \"Weather notification\". Find more information in the Gadgetbridge wiki. Applications list Blacklist Calendars + Blacklisted calendars will not be synced to the device Canned messages Replies Common suffix diff --git a/app/src/main/res/xml/devicesettings_sync_calendar.xml b/app/src/main/res/xml/devicesettings_sync_calendar.xml index 28b9bfb7a..f217315a6 100644 --- a/app/src/main/res/xml/devicesettings_sync_calendar.xml +++ b/app/src/main/res/xml/devicesettings_sync_calendar.xml @@ -6,4 +6,10 @@ android:key="sync_calendar" android:summary="@string/pref_summary_sync_calendar" android:title="@string/pref_title_sync_caldendar" /> + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 53eae5040..2ca7885e1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -194,15 +194,6 @@ - - eventList = new ArrayList<>(); - eventList.add(new CalendarEvents.CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false)); + List eventList = new ArrayList<>(); + eventList.add(new CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false)); GBDevice dummyGBDevice = createDummyGDevice("00:00:01:00:03"); dummyGBDevice.setState(GBDevice.State.INITIALIZED); @@ -49,7 +49,7 @@ public class CalendarEventTest extends TestBase { testCR.syncCalendar(eventList); - eventList.add(new CalendarEvents.CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false)); + eventList.add(new CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false)); testCR.syncCalendar(eventList); CalendarSyncStateDao calendarSyncStateDao = daoSession.getCalendarSyncStateDao();