diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java index 36cf3305e..3dcc55c01 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java @@ -451,6 +451,8 @@ public class QHybridSupport extends QHybridBaseSupport { public void onDeleteNotification(int id) { super.onDeleteNotification(id); + this.watchAdapter.onDeleteNotification(id); + showNotificationsByAllActive(true); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java index aa88203f9..a6de268f5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java @@ -135,4 +135,7 @@ public abstract class WatchAdapter { public void setBackgroundImage(byte[] pixels) { } + + public void onDeleteNotification(int id) { + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java index 2e18a850e..5699b0b8c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java @@ -3,11 +3,13 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fos import android.bluetooth.BluetoothGattCharacteristic; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.widget.Toast; @@ -43,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRActivitySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration; import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample; +import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; @@ -66,6 +69,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFile; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedGetRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FirmwareFilePutRequest; @@ -113,6 +117,9 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { private boolean saveRawActivityFiles = false; + HashMap appIconCache = new HashMap<>(); + String lastPostedApp = null; + @Override public void initialize() { saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false); @@ -299,7 +306,8 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { negotiateSymmetricKey(); ArrayList systemWidgets = new ArrayList<>(widgets.size()); for (Widget widget : this.widgets) { - if (!(widget instanceof CustomWidget)) systemWidgets.add(widget); + if (!(widget instanceof CustomWidget) && !widget.getWidgetType().isCustom()) + systemWidgets.add(widget); } queueWrite(new WidgetsPutRequest(systemWidgets.toArray(new Widget[0]), this)); } @@ -337,7 +345,41 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { for (int i = 0; i < this.widgets.size(); i++) { Widget w = widgets.get(i); if (!(w instanceof CustomWidget)) { - if (drawCircles) { + if (w.getWidgetType() == Widget.WidgetType.LAST_NOTIFICATION) { + Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888); + Canvas widgetCanvas = new Canvas(widgetBitmap); + if (drawCircles) { + widgetCanvas.drawBitmap(circleBitmap, 0, 0, null); + } + + Paint p = new Paint(); + p.setStyle(Paint.Style.FILL); + p.setTextSize(10); + p.setColor(Color.WHITE); + + if (this.lastPostedApp != null) { + + Bitmap icon = appIconCache.get(this.lastPostedApp); + + if (icon != null) { + + widgetCanvas.drawBitmap( + icon, + (float) (38 - (icon.getWidth() / 2.0)), + (float) (38 - (icon.getHeight() / 2.0)), + null + ); + } + } + + widgetImages.add(AssetImageFactory.createAssetImage( + widgetBitmap, + true, + w.getAngle(), + w.getDistance(), + 1 + )); + } else if (drawCircles) { widgetImages.add(AssetImageFactory.createAssetImage( circleBitmap, true, @@ -348,7 +390,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { } continue; } - ; + CustomWidget widget = (CustomWidget) w; Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888); @@ -411,9 +453,19 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { AssetImage[] images = widgetImages.toArray(new AssetImage[0]); - if(images.length > 0) { + ArrayList pushFiles = new ArrayList<>(4); + imgloop: + for (AssetImage image : images) { + for (AssetImage pushedImage : pushFiles) { + // no need to send same file multiple times, filtering by name since name is hash + if (image.getFileName().equals(pushedImage.getFileName())) continue imgloop; + } + pushFiles.add(image); + } + + if (pushFiles.size() > 0) { queueWrite(new AssetFilePutRequest( - images, + pushFiles.toArray(new AssetImage[0]), (byte) 0x00, this )); @@ -505,7 +557,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { syncSettings(); queueWrite(new VerifyPrivateKeyRequest(this.getSecretKey(), this)); - queueWrite(new FileLookupRequest((byte) 0x01, this){ + queueWrite(new FileLookupRequest((byte) 0x01, this) { @Override public void handleFileLookup(final short fileHandle) { queueWrite(new FileEncryptedGetRequest(fileHandle, FossilHRWatchAdapter.this) { @@ -519,14 +571,14 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { HybridHRActivitySample[] samples = new HybridHRActivitySample[entries.size()]; Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); - Long deviceId = DBHelper.getDevice(getDeviceSupport(). getDevice(), dbHandler.getDaoSession()).getId(); - for(int i = 0; i < entries.size(); i++){ + Long deviceId = DBHelper.getDevice(getDeviceSupport().getDevice(), dbHandler.getDaoSession()).getId(); + for (int i = 0; i < entries.size(); i++) { samples[i] = entries.get(i).toDAOActivitySample(userId, deviceId); } provider.addGBActivitySamples(samples); - if(saveRawActivityFiles) { + if (saveRawActivityFiles) { writeFile(String.valueOf(System.currentTimeMillis()), fileData); } queueWrite(new FileDeleteRequest(fileHandle)); @@ -542,9 +594,9 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { @Override public void handleFileLookupError(FILE_LOOKUP_ERROR error) { - if(error == FILE_LOOKUP_ERROR.FILE_EMPTY){ - GB.toast("activity file empty yet", Toast.LENGTH_LONG, GB.ERROR); - }else{ + if (error == FILE_LOOKUP_ERROR.FILE_EMPTY) { + GB.toast("activity file empty yet", Toast.LENGTH_LONG, GB.ERROR); + } else { throw new RuntimeException("strange lookup stuff"); } getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext()); @@ -552,7 +604,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { }); } - private void writeFile(String fileName, byte[] value){ + private void writeFile(String fileName, byte[] value) { File activityDir = new File(getContext().getExternalFilesDir(null), "activity_hr"); activityDir.mkdir(); File f = new File(activityDir, fileName); @@ -579,12 +631,14 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { } public boolean playRawNotification(NotificationSpec notificationSpec) { + String sourceAppId = notificationSpec.sourceAppId; + String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title); try { for (NotificationHRConfiguration configuration : this.notificationConfigurations) { - if (configuration.getPackageName().equals(notificationSpec.sourceAppId)) { - queueWrite(new PlayTextNotificationRequest(notificationSpec.sourceAppId, senderOrTitle, notificationSpec.body, this)); + if (configuration.getPackageName().equals(sourceAppId)) { + queueWrite(new PlayTextNotificationRequest(sourceAppId, senderOrTitle, notificationSpec.body, this)); return true; } } @@ -592,9 +646,43 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { } catch (Exception e) { e.printStackTrace(); } + + if (sourceAppId != null) { + if (!sourceAppId.equals(this.lastPostedApp)) { + if (appIconCache.get(sourceAppId) == null) { + try { + PackageManager pm = getContext().getPackageManager(); + Drawable icon = pm.getApplicationIcon(sourceAppId); + + Bitmap iconBitmap = Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888); + icon.setBounds(0, 0, 40, 40); + icon.draw(new Canvas(iconBitmap)); + + appIconCache.put(sourceAppId, iconBitmap); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + } + this.lastPostedApp = sourceAppId; + renderWidgets(); + } + } return true; } + @Override + public void onDeleteNotification(int id) { + super.onDeleteNotification(id); + + // only delete app icon when no notification of said app is present + for (String app : NotificationListener.notificationStack) { + if (app.equals(this.lastPostedApp)) return; + } + + this.lastPostedApp = null; + renderWidgets(); + } + @Override public void onFindDevice(boolean start) { if (start) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java index da99f51c4..07f3b2979 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java @@ -21,6 +21,10 @@ public class Widget implements Serializable { this.fontColor = fontColor; } + public WidgetType getWidgetType() { + return widgetType; + } + public int getAngle() { return angle; } @@ -47,8 +51,11 @@ public class Widget implements Serializable { JSONObject object = new JSONObject(); try { + String type; + if(widgetType == null) type = "Custom widget"; + else type = widgetType.getIdentifier(); object - .put("name", widgetType.getIdentifier()) + .put("name", type) .put("pos", new JSONObject() .put("angle", angle) @@ -75,14 +82,23 @@ public class Widget implements Serializable { CALORIES("caloriesSSE", R.string.hr_widget_calories), BATTERY("batterySSE", R.string.hr_widget_battery), WEATHER("weatherSSE", R.string.hr_widget_weather), + LAST_NOTIFICATION("last_notification", R.string.hr_widget_last_notification, true), NOTHING(null, R.string.hr_widget_nothing); private String identifier; private int stringResource; + private boolean custom; WidgetType(String identifier, int stringResource) { this.identifier = identifier; this.stringResource = stringResource; + this.custom = false; + } + + WidgetType(String identifier, int stringResource, boolean custom) { + this.identifier = identifier; + this.stringResource = stringResource; + this.custom = custom; } public static WidgetType fromJsonIdentifier(String jsonIdentifier){ @@ -99,5 +115,9 @@ public class Widget implements Serializable { public String getIdentifier() { return this.identifier; } + + public boolean isCustom() { + return custom; + } } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b71706a36..6604a1e00 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -906,4 +906,5 @@ Wähle die Verknüpfungen auf dem Band-Bildschirm Unbekannt Verknüpfungen + Letzte Benachrichtigung \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dc5f2fbaf..daebb6a52 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -861,6 +861,7 @@ Location access must be granted and enabled for scanning to work properly Draw widget circles Save raw activity files + Last notification %d hour %d hours