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 47d9e2d25..8305f04eb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -26,10 +26,12 @@ import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; @@ -40,6 +42,7 @@ import android.os.Handler; import android.os.PowerManager; import android.os.Process; import android.os.UserHandle; +import android.provider.MediaStore; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -53,6 +56,10 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -120,6 +127,17 @@ public class NotificationListener extends NotificationListenerService { add("mikado.bizcalpro"); }}; + private static final Set supportedPictureMimeTypes = new HashSet() {{ + add("image/"); //for im.vector.app + add("image/jpeg"); + add("image/png"); + add("image/gif"); + add("image/bmp"); + add("image/webp"); + }}; + + private File notificationPictureCacheDirectory; + public static ArrayList notificationStack = new ArrayList<>(); private static ArrayList notificationsActive = new ArrayList(); @@ -240,6 +258,8 @@ public class NotificationListener extends NotificationListenerService { filterLocal.addAction(ACTION_MUTE); filterLocal.addAction(ACTION_REPLY); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); + createNotificationPictureCacheDirectory(); + cleanUpNotificationPictureProvider(); } @Override @@ -247,6 +267,7 @@ public class NotificationListener extends NotificationListenerService { LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); notificationStack.clear(); notificationsActive.clear(); + cleanUpNotificationPictureProvider(); super.onDestroy(); } @@ -648,6 +669,41 @@ public class NotificationListener extends NotificationListenerService { notificationSpec.body = sanitizeUnicode(contentCS.toString()); } + NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification); + if (messagingStyle != null) { + List messages = messagingStyle.getMessages(); + if (!messages.isEmpty()) { + // Get the last message (assumed to be the most recent) + NotificationCompat.MessagingStyle.Message lastMessage = messages.get(messages.size() - 1); + + if (supportedPictureMimeTypes.contains(lastMessage.getDataMimeType())) { + ContentResolver contentResolver = getContentResolver(); + try (Cursor cursor = contentResolver.query(lastMessage.getDataUri(), null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + notificationSpec.picturePath = cursor.getString(dataIndex); + } + } catch (Exception e) { + LOG.error(e.getMessage()); + } + } + } + } + + if (extras.containsKey(NotificationCompat.EXTRA_PICTURE)) { + final Bitmap bmp = (Bitmap) extras.get(NotificationCompat.EXTRA_PICTURE); + File pictureFile = new File(this.notificationPictureCacheDirectory, String.valueOf(notificationSpec.getId())); + + try (FileOutputStream fos = new FileOutputStream(pictureFile)) { + bmp.compress(Bitmap.CompressFormat.PNG, 100, fos); + notificationSpec.picturePath = pictureFile.getAbsolutePath(); + } catch (IOException e) { + LOG.error("Failed to save picture to notification cache: {}", e.getMessage()); + } finally { + bmp.recycle(); + } + } + if (notificationSpec.type == NotificationType.COL_REMINDER && notificationSpec.body == null && notificationSpec.title != null) { @@ -775,6 +831,7 @@ public class NotificationListener extends NotificationListenerService { for (int notificationId : notificationsActive) { if (!activeNotificationsIds.contains(notificationId)) { notificationsToRemove.add(notificationId); + deleteNotificationPicture(notificationId); } } @@ -798,6 +855,27 @@ public class NotificationListener extends NotificationListenerService { } } + private void deleteNotificationPicture(int notificationId) { + File pictureFile = new File(this.notificationPictureCacheDirectory, String.valueOf(notificationId)); + if (pictureFile.exists()) + pictureFile.delete(); + } + + private void cleanUpNotificationPictureProvider() { + File[] pictureFiles = this.notificationPictureCacheDirectory.listFiles(); + if (pictureFiles == null) + return; + + for (File pictureFile : pictureFiles) { + pictureFile.delete(); + } + } + + private void createNotificationPictureCacheDirectory() { + final File cacheDir = getApplicationContext().getExternalCacheDir(); + this.notificationPictureCacheDirectory = new File(cacheDir, "notification-pictures"); + this.notificationPictureCacheDirectory.mkdir(); + } private void logNotification(StatusBarNotification sbn, boolean posted) { LOG.debug( "Notification {} {}: packageName={}, priority={}, category={}", diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index bf5b5280f..527a6273a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -179,6 +179,7 @@ public class GBDeviceService implements DeviceService { .putExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR, notificationSpec.pebbleColor) .putExtra(EXTRA_NOTIFICATION_SOURCEAPPID, notificationSpec.sourceAppId) .putExtra(EXTRA_NOTIFICATION_ICONID, notificationSpec.iconId) + .putExtra(NOTIFICATION_PICTURE_PATH, notificationSpec.picturePath) .putExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, notificationSpec.dndSuppressed); invokeService(intent); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 79c40d8f5..eb12e6c74 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -97,6 +97,7 @@ public interface DeviceService extends EventHandler { String EXTRA_NOTIFICATION_ACTIONS = "notification_actions"; String EXTRA_NOTIFICATION_PEBBLE_COLOR = "notification_pebble_color"; String EXTRA_NOTIFICATION_ICONID = "notification_iconid"; + String NOTIFICATION_PICTURE_PATH = "notification_picture_path"; String EXTRA_NOTIFICATION_DNDSUPPRESSED = "notification_dndsuppressed"; String EXTRA_FIND_START = "find_start"; String EXTRA_VIBRATION_INTENSITY = "vibration_intensity"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java index c46079285..778d4c7f6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java @@ -48,6 +48,7 @@ public class NotificationSpec { */ public int iconId; + public String picturePath; /** * The color that should be assigned to this notification when displayed on a Pebble */ 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 ecc0500c7..47c573bec 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -827,6 +827,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere notificationSpec.flags = intent.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0); notificationSpec.sourceAppId = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCEAPPID); notificationSpec.iconId = intent.getIntExtra(EXTRA_NOTIFICATION_ICONID, 0); + notificationSpec.picturePath = intent.getStringExtra(NOTIFICATION_PICTURE_PATH); notificationSpec.dndSuppressed = intent.getIntExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, 0); if (notificationSpec.type == NotificationType.GENERIC_SMS && notificationSpec.phoneNumber != null) {