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 0d34375b..0b32ba7e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -18,6 +18,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.externalevents; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; @@ -29,19 +30,17 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; import android.media.session.PlaybackState; +import android.os.Build; import android.os.Bundle; import android.os.PowerManager; -import android.os.RemoteException; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; +import android.support.v4.app.NotificationCompat; import android.support.v4.app.RemoteInput; import android.support.v4.content.LocalBroadcastManager; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaControllerCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; -import android.support.v7.app.NotificationCompat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,7 +76,7 @@ public class NotificationListener extends NotificationListenerService { private LimitedQueue mActionLookup = new LimitedQueue(16); private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - + @SuppressLint("NewApi") @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -90,7 +89,7 @@ public class NotificationListener extends NotificationListenerService { StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); int handle = intent.getIntExtra("handle", -1); for (StatusBarNotification sbn : sbns) { - if ((sbn.getPackageName().hashCode() * 31 + sbn.getId()) == handle) { + if ((int) sbn.getPostTime() == handle) { if (action.equals(ACTION_OPEN)) { try { PendingIntent pi = sbn.getNotification().contentIntent; @@ -113,7 +112,7 @@ public class NotificationListener extends NotificationListenerService { StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); int handle = intent.getIntExtra("handle", -1); for (StatusBarNotification sbn : sbns) { - if ((sbn.getPackageName().hashCode() * 31 + sbn.getId()) == handle) { + if ((int) sbn.getPostTime() == handle) { if (GBApplication.isRunningLollipopOrLater()) { String key = sbn.getKey(); NotificationListener.this.cancelNotification(key); @@ -178,8 +177,16 @@ public class NotificationListener extends NotificationListenerService { @Override public void onNotificationPosted(StatusBarNotification sbn) { - if (shouldIgnore(sbn)) + /* + * return early if DeviceCommunicationService is not running, + * else the service would get started every time we get a notification. + * unfortunately we cannot enable/disable NotificationListener at runtime like we do with + * broadcast receivers because it seems to invalidate the permissions that are + * necessary for NotificationListenerService + */ + if (!isServiceRunning()) { return; + } switch (GBApplication.getGrantedInterruptionFilter()) { case NotificationManager.INTERRUPTION_FILTER_ALL: @@ -194,8 +201,53 @@ public class NotificationListener extends NotificationListenerService { String source = sbn.getPackageName(); Notification notification = sbn.getNotification(); + + if (handleMediaSessionNotification(notification)) + return; + + Prefs prefs = GBApplication.getPrefs(); + if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) { + PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE); + if (powermanager.isScreenOn()) { +// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this"); + return; + } + } + + if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { +// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags); + return; + } + + /* do not display messages from "android" + * This includes keyboard selection message, usb connection messages, etc + * Hope it does not filter out too much, we will see... + */ + + if (source.equals("android") || + source.equals("com.android.systemui") || + source.equals("com.android.dialer") || + source.equals("com.cyanogenmod.eleven")) { + LOG.info("Not forwarding notification, is a system event"); + return; + } + + if (source.equals("com.moez.QKSMS") || + source.equals("com.android.mms") || + source.equals("com.sonyericsson.conversations") || + source.equals("com.android.messaging") || + source.equals("org.smssecure.smssecure")) { + if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) { + return; + } + } + + if (GBApplication.isBlacklisted(source)) { + LOG.info("Not forwarding notification, application is blacklisted"); + return; + } + NotificationSpec notificationSpec = new NotificationSpec(); - notificationSpec.id = source.hashCode() * 31 + sbn.getId(); // determinate Source App Name ("Label") PackageManager pm = getPackageManager(); @@ -209,17 +261,26 @@ public class NotificationListener extends NotificationListenerService { notificationSpec.sourceName = (String) pm.getApplicationLabel(ai); } - boolean preferBigText = true; //changed to true since now we update the former ID + boolean preferBigText = false; notificationSpec.type = AppNotificationType.getInstance().get(source); + if (source.startsWith("com.fsck.k9")) { + // we dont want group summaries at all for k9 + if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) { + return; + } + preferBigText = true; + } + if (notificationSpec.type == null) { notificationSpec.type = NotificationType.UNKNOWN; } - LOG.info("Processing notification " + notificationSpec.id + " from source " + source + " with flags: " + notification.flags); + LOG.info("Processing notification from source " + source + " with flags: " + notification.flags); dissectNotificationTo(notification, notificationSpec, preferBigText); + notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better // ignore Gadgetbridge's very own notifications, except for those from the debug screen if (getApplicationContext().getPackageName().equals(source)) { @@ -231,6 +292,11 @@ public class NotificationListener extends NotificationListenerService { NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification); List actions = wearableExtender.getActions(); + if (actions.isEmpty() && (notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) { //this could cause #395 to come back + LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags); + return; + } + for (NotificationCompat.Action act : actions) { if (act != null && act.getRemoteInputs() != null) { LOG.info("found wearable action: " + act.getTitle() + " " + sbn.getTag()); @@ -240,17 +306,11 @@ public class NotificationListener extends NotificationListenerService { } } - if ((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) == 0 && NotificationCompat.isGroupSummary(notification)) { //this could cause #395 to come back - LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags); - return; - } - GBApplication.deviceService().onNotification(notificationSpec); } private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec, boolean preferBigText) { - - Bundle extras = NotificationCompat.getExtras(notification); + Bundle extras = notification.extras; //dumpExtras(extras); @@ -261,9 +321,9 @@ public class NotificationListener extends NotificationListenerService { CharSequence contentCS = null; if (preferBigText && extras.containsKey(Notification.EXTRA_BIG_TEXT)) { - contentCS = extras.getCharSequence(NotificationCompat.EXTRA_BIG_TEXT); + contentCS = extras.getCharSequence(Notification.EXTRA_BIG_TEXT); } else if (extras.containsKey(Notification.EXTRA_TEXT)) { - contentCS = extras.getCharSequence(NotificationCompat.EXTRA_TEXT); + contentCS = extras.getCharSequence(Notification.EXTRA_TEXT); } if (contentCS != null) { notificationSpec.body = contentCS.toString(); @@ -284,18 +344,31 @@ public class NotificationListener extends NotificationListenerService { /** * Try to handle media session notifications that tell info about the current play state. * - * @param mediaSession The mediasession to handle. + * @param notification The notification to handle. * @return true if notification was handled, false otherwise */ - public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) { + public boolean handleMediaSessionNotification(Notification notification) { + + // this code requires Android 5.0 or newer + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return false; + } + MusicSpec musicSpec = new MusicSpec(); MusicStateSpec stateSpec = new MusicStateSpec(); - MediaControllerCompat c; - try { - c = new MediaControllerCompat(getApplicationContext(), mediaSession); + Bundle extras = notification.extras; + if (extras == null) + return false; - PlaybackStateCompat s = c.getPlaybackState(); + if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null) + return false; + + MediaController c; + try { + c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION)); + + PlaybackState s = c.getPlaybackState(); stateSpec.position = (int) (s.getPosition() / 1000); stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); stateSpec.repeat = 1; @@ -315,41 +388,57 @@ public class NotificationListener extends NotificationListenerService { break; } - MediaMetadataCompat d = c.getMetadata(); + MediaMetadata d = c.getMetadata(); if (d == null) return false; if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) - musicSpec.artist = d.getString(MediaMetadataCompat.METADATA_KEY_ARTIST); + musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST); if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) - musicSpec.album = d.getString(MediaMetadataCompat.METADATA_KEY_ALBUM); + musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM); if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) - musicSpec.track = d.getString(MediaMetadataCompat.METADATA_KEY_TITLE); + musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) - musicSpec.duration = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) / 1000; + musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) - musicSpec.trackCount = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS); + musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS); if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) - musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER); + musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER); // finally, tell the device about it GBApplication.deviceService().onSetMusicInfo(musicSpec); GBApplication.deviceService().onSetMusicState(stateSpec); return true; - } catch (NullPointerException | RemoteException e) { + } catch (NullPointerException e) { return false; } } @Override public void onNotificationRemoved(StatusBarNotification sbn) { - if (shouldIgnore(sbn)) + //FIXME: deduplicate code + if (!isServiceRunning() || sbn == null) { return; + } + + String source = sbn.getPackageName(); + Notification notification = sbn.getNotification(); + if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { + return; + } + + if (source.equals("android") || + source.equals("com.android.systemui") || + source.equals("com.android.dialer") || + source.equals("com.cyanogenmod.eleven")) { + return; + } Prefs prefs = GBApplication.getPrefs(); if (prefs.getBoolean("autoremove_notifications", false)) { LOG.info("notification removed, will ask device to delete it"); - GBApplication.deviceService().onDeleteNotification(sbn.getPackageName().hashCode() * 31 + sbn.getId()); + + GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime()); //FIMXE: a truly unique id would be better } } @@ -362,88 +451,4 @@ public class NotificationListener extends NotificationListenerService { LOG.debug(String.format("Notification extra: %s %s (%s)", key, value.toString(), value.getClass().getName())); } } - - private boolean shouldIgnore(StatusBarNotification sbn) { - /* - * return early if DeviceCommunicationService is not running, - * else the service would get started every time we get a notification. - * unfortunately we cannot enable/disable NotificationListener at runtime like we do with - * broadcast receivers because it seems to invalidate the permissions that are - * necessary for NotificationListenerService - */ - if (!isServiceRunning() || sbn == null) { - return true; - } - - if (shouldIgnoreSource(sbn.getPackageName())) - return true; - - if (shouldIgnoreNotification(sbn.getNotification())) - return true; - - return false; - } - - private boolean shouldIgnoreSource(String source) { - Prefs prefs = GBApplication.getPrefs(); - - /* do not display messages from "android" - * This includes keyboard selection message, usb connection messages, etc - * Hope it does not filter out too much, we will see... - */ - - if (source.equals("android") || - source.equals("com.android.systemui") || - source.equals("com.android.dialer") || - source.equals("com.cyanogenmod.eleven")) { - LOG.info("Ignoring notification, is a system event"); - return true; - } - - if (source.equals("com.moez.QKSMS") || - source.equals("com.android.mms") || - source.equals("com.sonyericsson.conversations") || - source.equals("com.android.messaging") || - source.equals("org.smssecure.smssecure")) { - if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) { - return true; - } - } - - if (GBApplication.isBlacklisted(source)) { - LOG.info("Ignoring notification, application is blacklisted"); - return true; - } - - return false; - } - - private boolean shouldIgnoreNotification(Notification notification) { - - MediaSessionCompat.Token mediaSession = NotificationCompat.getMediaSession(notification); - //try to handle media session notifications - if (mediaSession != null && handleMediaSessionNotification(mediaSession)) - return true; - - //ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY - if (NotificationCompat.getLocalOnly(notification)) - return true; - - Prefs prefs = GBApplication.getPrefs(); - if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) { - PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE); - if (powermanager.isScreenOn()) { -// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this"); - return true; - } - } - - if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { -// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags); - return true; - } - - return false; - } - } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index 8ed534d5..b3b21b30 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -148,7 +148,6 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { } } if (reconnect()) { - super.onDeleteNotification(notificationSpec.id); //update notification hack super.onNotification(notificationSpec); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index fce8a881..2ae943e5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -76,7 +76,7 @@ public class GB { .setContentIntent(pendingIntent) .setOngoing(true); if (GBApplication.isRunningLollipopOrLater()) { - builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + builder.setVisibility(Notification.VISIBILITY_PUBLIC); } if (GBApplication.minimizeNotification()) { builder.setPriority(Notification.PRIORITY_MIN); @@ -268,7 +268,7 @@ public class GB { notificationIntent, 0); NotificationCompat.Builder nb = new NotificationCompat.Builder(context) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setVisibility(Notification.VISIBILITY_PUBLIC) .setContentTitle(context.getString(R.string.app_name)) .setContentText(text) .setContentIntent(pendingIntent)