Garmin/Zepp OS: Background notifications

This commit is contained in:
José Rebelo 2024-12-24 16:33:48 +00:00
parent f99b43fc56
commit 0a9d08f67b
12 changed files with 92 additions and 26 deletions

View File

@ -875,6 +875,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return R.drawable.ic_device_default_disabled;
}
@Override
public boolean supportsBackgroundNotifications(final GBDevice device) {
return false;
}
@Override
public boolean supportsNotificationVibrationPatterns() {
return false;

View File

@ -786,6 +786,11 @@ public interface DeviceCoordinator {
@DrawableRes
int getDisabledIconResource();
/**
* Whether the device supports silent background notifications.
*/
boolean supportsBackgroundNotifications(GBDevice device);
/**
* Whether the device supports a variety of vibration patterns for notifications.
*/

View File

@ -372,6 +372,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
return true;
}
@Override
public boolean supportsBackgroundNotifications(final GBDevice device) {
return true;
}
@Override
public int getCannedRepliesSlotCount(final GBDevice device) {
if (getPrefs(device).getBoolean(GarminPreferences.PREF_FEAT_CANNED_MESSAGES, false)) {

View File

@ -68,6 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsLogsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsLoyaltyCardService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsNotificationService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsRemindersService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
@ -621,6 +622,11 @@ public abstract class ZeppOsCoordinator extends HuamiCoordinator {
return getPrefs(device).getBoolean("zepp_os_experimental_features", false);
}
@Override
public boolean supportsBackgroundNotifications(final GBDevice device) {
return ZeppOsNotificationService.supportsBackgroundNotifications(getPrefs(device));
}
@Override
public boolean validateAuthKey(final String authKey) {
final byte[] authKeyBytes = authKey.trim().getBytes();

View File

@ -54,7 +54,6 @@ 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;
@ -354,6 +353,8 @@ public class NotificationListener extends NotificationListenerService {
return;
}
NotificationSpec notificationSpec = new NotificationSpec();
// Ignore too frequent notifications, according to user preference
long curTime = System.nanoTime();
Long notificationBurstPreventionValue = notificationBurstPrevention.get(source);
@ -361,11 +362,10 @@ public class NotificationListener extends NotificationListenerService {
long diff = curTime - notificationBurstPreventionValue;
if (diff < TimeUnit.SECONDS.toNanos(prefs.getInt("notifications_timeout", 0))) {
LOG.info("Ignoring frequent notification, last one was {} ms ago", TimeUnit.NANOSECONDS.toMillis(diff));
return;
notificationSpec.background = true;
}
}
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.key = sbn.getKey();
notificationSpec.when = notification.when;

View File

@ -180,7 +180,8 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_NOTIFICATION_SOURCEAPPID, notificationSpec.sourceAppId)
.putExtra(EXTRA_NOTIFICATION_ICONID, notificationSpec.iconId)
.putExtra(NOTIFICATION_PICTURE_PATH, notificationSpec.picturePath)
.putExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, notificationSpec.dndSuppressed);
.putExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, notificationSpec.dndSuppressed)
.putExtra(EXTRA_NOTIFICATION_BACKGROUND, notificationSpec.background);
invokeService(intent);
}

View File

@ -101,6 +101,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_NOTIFICATION_ICONID = "notification_iconid";
String NOTIFICATION_PICTURE_PATH = "notification_picture_path";
String EXTRA_NOTIFICATION_DNDSUPPRESSED = "notification_dndsuppressed";
String EXTRA_NOTIFICATION_BACKGROUND = "notification_background";
String EXTRA_FIND_START = "find_start";
String EXTRA_VIBRATION_INTENSITY = "vibration_intensity";
String EXTRA_CALL_COMMAND = "call_command";

View File

@ -56,6 +56,8 @@ public class NotificationSpec {
public int dndSuppressed;
public boolean background;
public NotificationSpec() {
this(-1);
}

View File

@ -813,6 +813,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
DeviceSupport deviceSupport = getDeviceSupport(device);
DeviceCoordinator coordinator = getDeviceCoordinator(device);
Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
@ -861,6 +862,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
notificationSpec.iconId = intentCopy.getIntExtra(EXTRA_NOTIFICATION_ICONID, 0);
notificationSpec.picturePath = intent.getStringExtra(NOTIFICATION_PICTURE_PATH);
notificationSpec.dndSuppressed = intentCopy.getIntExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, 0);
notificationSpec.background = intentCopy.getBooleanExtra(EXTRA_NOTIFICATION_BACKGROUND, false);
if (notificationSpec.type == NotificationType.GENERIC_SMS && notificationSpec.phoneNumber != null) {
GBApplication.getIDSenderLookup().add(notificationSpec.getId(), notificationSpec.phoneNumber);
@ -882,7 +884,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
notificationSpec.cannedReplies = replies.toArray(new String[0]);
}
deviceSupport.onNotification(notificationSpec);
if (!notificationSpec.background || coordinator.supportsBackgroundNotifications(device)) {
deviceSupport.onNotification(notificationSpec);
}
break;
}
case ACTION_DELETE_NOTIFICATION: {

View File

@ -118,7 +118,15 @@ public class NotificationsHandler implements MessageHandler {
}
final boolean hasPicture = !StringUtils.isEmpty(notificationSpec.picturePath);
return new NotificationUpdateMessage(notificationUpdateType, notificationSpec.type, getNotificationsCount(notificationSpec.type), notificationSpec.getId(), hasActions, hasPicture);
return new NotificationUpdateMessage(
notificationUpdateType,
notificationSpec.type,
getNotificationsCount(notificationSpec.type),
notificationSpec.getId(),
notificationSpec.background,
hasActions,
hasPicture
);
}
private int getNotificationsCount(NotificationType notificationType) {
@ -155,7 +163,14 @@ public class NotificationsHandler implements MessageHandler {
NotificationSpec e = iterator.next();
if (e.getId() == id) {
iterator.remove();
return new NotificationUpdateMessage(NotificationUpdateMessage.NotificationUpdateType.REMOVE, e.type, getNotificationsCount(e.type), id, false, false);
return new NotificationUpdateMessage(
NotificationUpdateMessage.NotificationUpdateType.REMOVE,
e.type, getNotificationsCount(e.type),
id,
false,
false,
false
);
}
}
return null;

View File

@ -13,15 +13,23 @@ public class NotificationUpdateMessage extends GFDIMessage {
final private int count; //how many notifications of the same type are present
final private int notificationId;
final private boolean hasActions;
final private boolean background;
final private boolean hasPicture;
final private boolean useLegacyActions = false;
public NotificationUpdateMessage(NotificationUpdateType notificationUpdateType, NotificationType notificationType, int count, int notificationId, boolean hasActions, boolean hasPicture) {
public NotificationUpdateMessage(NotificationUpdateType notificationUpdateType,
NotificationType notificationType,
int count,
int notificationId,
boolean background,
boolean hasActions,
boolean hasPicture) {
this.garminMessage = GarminMessage.NOTIFICATION_UPDATE;
this.notificationUpdateType = notificationUpdateType;
this.notificationType = notificationType;
this.count = count;
this.notificationId = notificationId;
this.background = background;
this.hasActions = hasActions;
this.hasPicture = hasPicture;
}
@ -32,7 +40,7 @@ public class NotificationUpdateMessage extends GFDIMessage {
writer.writeShort(0); // packet size will be filled below
writer.writeShort(this.garminMessage.getId());
writer.writeByte(this.notificationUpdateType.ordinal());
writer.writeByte(getCategoryFlags(this.notificationType));
writer.writeByte(getCategoryFlags(this.notificationType, this.background));
writer.writeByte(getCategoryValue(this.notificationType));
writer.writeByte(this.count);
writer.writeInt(this.notificationId);
@ -54,28 +62,33 @@ public class NotificationUpdateMessage extends GFDIMessage {
}
private int getCategoryFlags(NotificationType notificationType) {
private int getCategoryFlags(NotificationType notificationType, boolean background) {
EnumSet<NotificationFlag> flags = EnumSet.noneOf(NotificationFlag.class);
if (this.hasActions && this.useLegacyActions) { //only needed for legacy actions
flags.add(NotificationFlag.ACTION_ACCEPT);
}
flags.add(NotificationFlag.ACTION_DECLINE);
switch (notificationType.getGenericType()) {
case "generic_phone":
case "generic_email":
case "generic_sms":
case "generic_chat":
flags.add(NotificationFlag.FOREGROUND);
break;
case "generic_navigation":
case "generic_social":
case "generic_alarm_clock":
case "generic":
// TODO: Maybe make this configurable, but most users expect all notifications
// to be foreground, sending them as background was generating bug reports.
flags.add(NotificationFlag.FOREGROUND);
if (background) {
flags.add(NotificationFlag.BACKGROUND);
} else {
switch (notificationType.getGenericType()) {
case "generic_phone":
case "generic_email":
case "generic_sms":
case "generic_chat":
flags.add(NotificationFlag.FOREGROUND);
break;
case "generic_navigation":
case "generic_social":
case "generic_alarm_clock":
case "generic":
// TODO: Maybe make this configurable, but most users expect all notifications
// to be foreground, sending them as background was generating bug reports.
flags.add(NotificationFlag.FOREGROUND);
}
}
return (int) EnumUtils.generateBitVector(NotificationFlag.class, flags);
}

View File

@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
@ -47,9 +48,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class ZeppOsNotificationService extends AbstractZeppOsService {
@ -78,6 +79,8 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
public static final byte NOTIFICATION_CALL_STATE_START = 0x00;
public static final byte NOTIFICATION_CALL_STATE_END = 0x02;
public static final String PREF_VERSION = "zepp_os_notifications_version";
private int version = -1;
private boolean supportsPictures = false;
private boolean supportsNotificationKey = false;
@ -121,6 +124,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
switch (cmd) {
case NOTIFICATION_CMD_CAPABILITIES_RESPONSE: {
version = buf.get() & 0xff;
getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
if (version < 4 || version > 5) {
// Untested, might work, might not..
LOG.warn("Unsupported notification service version {}", version);
@ -357,7 +361,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
baos.write((byte) (hasReply ? 1 : 0));
if (version >= 5) {
baos.write(0); // 1 for silent
baos.write(notificationSpec.background ? 1 : 0); // 1 for silent
}
if (supportsPictures) {
baos.write((byte) (notificationSpec.picturePath != null ? 1 : 0));
@ -597,4 +601,9 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
return null;
}
}
public static boolean supportsBackgroundNotifications(final Prefs devicePrefs) {
final int version = devicePrefs.getInt(PREF_VERSION, 0);
return version >= 5;
}
}