diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 1e6777ccb..8a9e2c980 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -22,14 +22,12 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.externalevents; -import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; @@ -53,6 +51,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.LocalTime; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -81,6 +80,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; +import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.MediaManager; import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils; @@ -120,8 +120,8 @@ public class NotificationListener extends NotificationListenerService { add("mikado.bizcalpro"); }}; - public static ArrayList notificationStack = new ArrayList<>(); - private static ArrayList notificationsActive = new ArrayList(); + public static final ArrayList notificationStack = new ArrayList<>(); + private static final ArrayList notificationsActive = new ArrayList<>(); private long activeCallPostTime; private int mLastCallCommand = CallSpec.CALL_UNDEFINED; @@ -130,7 +130,7 @@ public class NotificationListener extends NotificationListenerService { private Runnable mSetMusicInfoRunnable = null; private Runnable mSetMusicStateRunnable = null; - private GoogleMapsNotificationHandler googleMapsNotificationHandler = new GoogleMapsNotificationHandler(); + private final GoogleMapsNotificationHandler googleMapsNotificationHandler = new GoogleMapsNotificationHandler(); private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -163,8 +163,8 @@ public class NotificationListener extends NotificationListenerService { if (pi != null) { pi.send(); } - } catch (PendingIntent.CanceledException e) { - e.printStackTrace(); + } catch (final PendingIntent.CanceledException e) { + LOG.error("Failed to open notification {}", sbn.getId()); } } } @@ -176,7 +176,7 @@ public class NotificationListener extends NotificationListenerService { LOG.info("could not lookup handle for mute action"); break; } - LOG.info("going to mute " + packageName); + LOG.info("going to mute {}", packageName); if (GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true")) { GBApplication.addAppToNotifBlacklist(packageName); } else { @@ -206,6 +206,10 @@ public class NotificationListener extends NotificationListenerService { String reply = intent.getStringExtra("reply"); if (wearableAction != null) { PendingIntent actionIntent = wearableAction.getActionIntent(); + if (actionIntent == null) { + LOG.warn("Action intent is null"); + break; + } Intent localIntent = new Intent(); localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (wearableAction.getRemoteInputs() != null && wearableAction.getRemoteInputs().length > 0) { @@ -218,8 +222,8 @@ public class NotificationListener extends NotificationListenerService { LOG.info("will send exec intent to remote application"); actionIntent.send(context, 0, localIntent); mActionLookup.remove(handle); - } catch (PendingIntent.CanceledException e) { - LOG.warn("replyToLastNotification error: " + e.getLocalizedMessage()); + } catch (final PendingIntent.CanceledException e) { + LOG.warn("replyToLastNotification error", e); } } else { LOG.warn("Received ACTION_REPLY but cannot find the corresponding wearableAction"); @@ -264,7 +268,11 @@ public class NotificationListener extends NotificationListenerService { if (isServiceNotRunningAndShouldIgnoreNotifications()) return; - final Prefs prefs = GBApplication.getPrefs(); + final GBPrefs prefs = GBApplication.getPrefs(); + + if (isOutsideNotificationTimes(prefs)) { + return; + } final boolean ignoreWorkProfile = prefs.getBoolean("notifications_ignore_work_profile", false); if (ignoreWorkProfile && isWorkProfile(sbn)) { @@ -458,6 +466,30 @@ public class NotificationListener extends NotificationListenerService { GBApplication.deviceService().onNotification(notificationSpec); } + private static boolean isOutsideNotificationTimes(final GBPrefs prefs) { + if (!prefs.getNotificationTimesEnabled()) { + return false; + } + + final LocalTime now = LocalTime.now(); + final LocalTime start = prefs.getNotificationTimesStart(); + final LocalTime end = prefs.getNotificationTimesEnd(); + final boolean shouldIgnore; + if (start.isBefore(end)) { + // eg. 06:00 -> 22:00 + shouldIgnore = now.isAfter(start) && now.isBefore(end); + } else { + // goes past midnight, eg. 22:00 -> 06:00 + shouldIgnore = now.isAfter(start) || now.isBefore(end); + } + + if (shouldIgnore) { + LOG.debug("Ignoring notification outside of notification times {}/{}", start, end); + } + + return shouldIgnore; + } + private boolean checkNotificationContentForWhiteAndBlackList(String packageName, String body) { long start = System.currentTimeMillis(); @@ -707,7 +739,11 @@ public class NotificationListener extends NotificationListenerService { if (isServiceNotRunningAndShouldIgnoreNotifications()) return; - final Prefs prefs = GBApplication.getPrefs(); + final GBPrefs prefs = GBApplication.getPrefs(); + + if (isOutsideNotificationTimes(prefs)) { + return; + } final boolean ignoreWorkProfile = prefs.getBoolean("notifications_ignore_work_profile", false); if (ignoreWorkProfile && isWorkProfile(sbn)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java index 00d59486c..402902f80 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java @@ -31,6 +31,7 @@ import android.util.Log; import androidx.core.app.ActivityCompat; import java.text.ParseException; +import java.time.LocalTime; import java.util.Date; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -153,4 +154,16 @@ public class GBPrefs extends Prefs { } return new float[]{longitude, latitude}; } + + public boolean getNotificationTimesEnabled() { + return getBoolean("notification_times_enabled", false); + } + + public LocalTime getNotificationTimesStart() { + return getLocalTime("notification_times_start", "08:00"); + } + + public LocalTime getNotificationTimesEnd() { + return getLocalTime("notification_times_end", "22:00"); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java index 4769fba7e..59376ea2d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java @@ -21,8 +21,11 @@ import android.util.Log; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.LocalTime; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -178,6 +181,7 @@ public class Prefs { return getList(key, defaultValue, ","); } + @Deprecated // use getLocalTime public Date getTimePreference(final String key, final String defaultValue) { final String time = getString(key, defaultValue); @@ -191,6 +195,23 @@ public class Prefs { return new Date(); } + public LocalTime getLocalTime(final String key, final String defaultValue) { + final String time = getString(key, defaultValue); + + final DateFormat df = new SimpleDateFormat("HH:mm", Locale.ROOT); + try { + final Date parse = df.parse(time); + final Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(parse); + + return LocalTime.of(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), 0); + } catch (final Exception e) { + Log.e(TAG, "Error reading localtime preference value: " + key + "; returning default current time", e); // log the first exception + } + + return LocalTime.now(); + } + private void logReadError(String key, Exception ex) { Log.e(TAG, "Error reading preference value: " + key + "; returning default value", ex); // log the first exception } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b7d28977..82e68c0d6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,6 +269,8 @@ Send missed notifications when a device reconnects after being out of range Do Not Disturb Block all notifications when Do Not Disturb is enabled on the phone + Notification times + Only send notifications between specific times Media notifications ignore app list Process media notifications before the app list. If this preference is unchecked, media applications need to be allowed in the application list for media controls to work on the device. Per application settings diff --git a/app/src/main/res/xml/notifications_preferences.xml b/app/src/main/res/xml/notifications_preferences.xml index b775f9a19..2c288c203 100644 --- a/app/src/main/res/xml/notifications_preferences.xml +++ b/app/src/main/res/xml/notifications_preferences.xml @@ -130,6 +130,34 @@ android:summary="@string/pref_summary_notification_filter" android:title="@string/pref_title_notification_filter" app:iconSpaceReserved="false" /> + + + + + + + + + +