1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-19 11:30:44 +02:00

Add support for picture attachments in notifications

Also adds a local cache for pictures which is handled by the NotificationListener itself.

If the picture is hosted in a third party content provider it gets queried.
If the Bitmap is embedded in the notification, the local cache is used.

In any case the NotificationSpec just holds a String to the absolute file path
This commit is contained in:
Daniele Gobbetti 2024-05-04 17:37:28 +02:00
parent db33bc3ee9
commit 1435c3a937
5 changed files with 82 additions and 0 deletions

View File

@ -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<String> supportedPictureMimeTypes = new HashSet<String>() {{
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<String> notificationStack = new ArrayList<>();
private static ArrayList<Integer> notificationsActive = new ArrayList<Integer>();
@ -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<NotificationCompat.MessagingStyle.Message> 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={}",

View File

@ -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);
}

View File

@ -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";

View File

@ -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
*/

View File

@ -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) {