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 0b32ba7ed..0f1cb7fc1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
@@ -18,7 +18,6 @@
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;
@@ -30,17 +29,20 @@ 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.Parcelable;
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;
@@ -76,7 +78,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();
@@ -89,7 +91,7 @@ public class NotificationListener extends NotificationListenerService {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
- if ((int) sbn.getPostTime() == handle) {
+ if ((sbn.getPackageName().hashCode() * 31 + sbn.getId()) == handle) {
if (action.equals(ACTION_OPEN)) {
try {
PendingIntent pi = sbn.getNotification().contentIntent;
@@ -112,7 +114,7 @@ public class NotificationListener extends NotificationListenerService {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
- if ((int) sbn.getPostTime() == handle) {
+ if ((sbn.getPackageName().hashCode() * 31 + sbn.getId()) == handle) {
if (GBApplication.isRunningLollipopOrLater()) {
String key = sbn.getKey();
NotificationListener.this.cancelNotification(key);
@@ -177,16 +179,8 @@ public class NotificationListener extends NotificationListenerService {
@Override
public void onNotificationPosted(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()) {
+ if (shouldIgnore(sbn))
return;
- }
switch (GBApplication.getGrantedInterruptionFilter()) {
case NotificationManager.INTERRUPTION_FILTER_ALL:
@@ -201,53 +195,8 @@ 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();
@@ -261,26 +210,17 @@ public class NotificationListener extends NotificationListenerService {
notificationSpec.sourceName = (String) pm.getApplicationLabel(ai);
}
- boolean preferBigText = false;
+ boolean preferBigText = true; //changed to true since now we update the former ID
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 from source " + source + " with flags: " + notification.flags);
+ LOG.info("Processing notification " + notificationSpec.id + " 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)) {
@@ -292,11 +232,6 @@ 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());
@@ -306,11 +241,17 @@ 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 = notification.extras;
+
+ Bundle extras = NotificationCompat.getExtras(notification);
//dumpExtras(extras);
@@ -320,10 +261,32 @@ public class NotificationListener extends NotificationListenerService {
}
CharSequence contentCS = null;
- if (preferBigText && extras.containsKey(Notification.EXTRA_BIG_TEXT)) {
- contentCS = extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
- } else if (extras.containsKey(Notification.EXTRA_TEXT)) {
- contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
+ if (extras.containsKey(Notification.EXTRA_MESSAGES)) {
+ Parcelable[] parcelables = extras.getParcelableArray(NotificationCompat.EXTRA_MESSAGES);
+ String contentBuilder = "";
+ CharSequence sender;
+ CharSequence prevSender = "";
+ CharSequence message;
+ for (Parcelable p : parcelables) {
+ if (!(p instanceof Bundle))
+ continue;
+ sender = ((Bundle) p).getCharSequence("sender");
+ message = ((Bundle) p).getCharSequence("text");
+ if (sender == null || message == null)
+ continue;
+ if (!sender.equals(prevSender) && !sender.equals(notificationSpec.title)) {
+ contentBuilder += sender.toString() + ": ";
+ prevSender = sender;
+ }
+ contentBuilder += message.toString() + "\n";
+ }
+ contentCS = contentBuilder;
+ } else {
+ if (preferBigText && extras.containsKey(Notification.EXTRA_BIG_TEXT)) {
+ contentCS = extras.getCharSequence(NotificationCompat.EXTRA_BIG_TEXT);
+ } else if (extras.containsKey(Notification.EXTRA_TEXT)) {
+ contentCS = extras.getCharSequence(NotificationCompat.EXTRA_TEXT);
+ }
}
if (contentCS != null) {
notificationSpec.body = contentCS.toString();
@@ -344,31 +307,18 @@ public class NotificationListener extends NotificationListenerService {
/**
* Try to handle media session notifications that tell info about the current play state.
*
- * @param notification The notification to handle.
+ * @param mediaSession The mediasession to handle.
* @return true if notification was handled, false otherwise
*/
- public boolean handleMediaSessionNotification(Notification notification) {
-
- // this code requires Android 5.0 or newer
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return false;
- }
-
+ public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) {
MusicSpec musicSpec = new MusicSpec();
MusicStateSpec stateSpec = new MusicStateSpec();
- Bundle extras = notification.extras;
- if (extras == null)
- return false;
-
- if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null)
- return false;
-
- MediaController c;
+ MediaControllerCompat c;
try {
- c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION));
+ c = new MediaControllerCompat(getApplicationContext(), mediaSession);
- PlaybackState s = c.getPlaybackState();
+ PlaybackStateCompat s = c.getPlaybackState();
stateSpec.position = (int) (s.getPosition() / 1000);
stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed());
stateSpec.repeat = 1;
@@ -388,57 +338,41 @@ public class NotificationListener extends NotificationListenerService {
break;
}
- MediaMetadata d = c.getMetadata();
+ MediaMetadataCompat d = c.getMetadata();
if (d == null)
return false;
if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST))
- musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ musicSpec.artist = d.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM))
- musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM);
+ musicSpec.album = d.getString(MediaMetadataCompat.METADATA_KEY_ALBUM);
if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE))
- musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE);
+ musicSpec.track = d.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION))
- musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000;
+ musicSpec.duration = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) / 1000;
if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
- musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+ musicSpec.trackCount = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS);
if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
- musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+ musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
// finally, tell the device about it
GBApplication.deviceService().onSetMusicInfo(musicSpec);
GBApplication.deviceService().onSetMusicState(stateSpec);
return true;
- } catch (NullPointerException e) {
+ } catch (NullPointerException | RemoteException e) {
return false;
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
- //FIXME: deduplicate code
- if (!isServiceRunning() || sbn == null) {
+ if (shouldIgnore(sbn))
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((int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
+ GBApplication.deviceService().onDeleteNotification(sbn.getPackageName().hashCode() * 31 + sbn.getId());
}
}
@@ -451,4 +385,88 @@ 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 b3b21b30f..8ed534d5d 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,6 +148,7 @@ 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 2ae943e5f..fce8a881d 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(Notification.VISIBILITY_PUBLIC);
+ builder.setVisibility(NotificationCompat.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(Notification.VISIBILITY_PUBLIC)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(context.getString(R.string.app_name))
.setContentText(text)
.setContentIntent(pendingIntent)