diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java index 447e9a1b2..205a26cfa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java @@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; @@ -153,6 +154,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin return huaweiCoordinator.getContactsSlotCount(device); } + @Override + public int getCannedRepliesSlotCount(GBDevice device) { + return huaweiCoordinator.getCannedRepliesSlotCount(device); + } + @Override public boolean supportsCalendarEvents() { return huaweiCoordinator.supportsCalendarEvents(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java index 8d4c9e1ec..aee2e0963 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java @@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample; import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.util.GB; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; @@ -285,6 +286,9 @@ public class HuaweiCoordinator { if (supportsNotificationsRepeatedNotify() || supportsNotificationsRemoveSingle()){ notifications.add(R.xml.devicesettings_autoremove_notifications); } + if (getCannedRepliesSlotCount(device) > 0) { + notifications.add(R.xml.devicesettings_canned_reply_16); + } if (supportsNotificationOnBluetoothLoss()) notifications.add(R.xml.devicesettings_disconnectnotification_noshed); if (supportsDoNotDisturb(device)) @@ -681,13 +685,18 @@ public class HuaweiCoordinator { return supportsExpandCapability(89); return false; } + public boolean supportsNotificationsRemoveSingle() { if (supportsExpandCapability()) return supportsExpandCapability(120); return false; } - + public boolean supportsCannedReplies() { + if (supportsExpandCapability()) + return supportsExpandCapability(82); + return false; + } public boolean supportsPromptPushMessage () { // do not ask for capabilities under specific condition @@ -758,6 +767,11 @@ public class HuaweiCoordinator { return supportsContacts()?maxContactsCount:0; } + public int getCannedRepliesSlotCount(GBDevice device) { + // TODO: find proper count + return supportsCannedReplies()?10:0; + } + public void setTransactionCrypted(boolean crypted) { this.transactionCrypted = crypted; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java index 13d06cc23..4ec60980f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java @@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; @@ -162,6 +163,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i return huaweiCoordinator.getContactsSlotCount(device); } + @Override + public int getCannedRepliesSlotCount(GBDevice device) { + return huaweiCoordinator.getCannedRepliesSlotCount(device); + } + @Override public boolean supportsCalendarEvents() { return huaweiCoordinator.supportsCalendarEvents(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java index a06adf7e2..c9c7775cc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java @@ -31,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; @@ -215,4 +216,9 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport { public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) { supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds); } + + @Override + public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) { + supportProvider.onSetCannedMessages(cannedMessagesSpec); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java index 7f239194c..d770986a9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java @@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; @@ -224,4 +225,9 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport { public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) { supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds); } + + @Override + public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) { + supportProvider.onSetCannedMessages(cannedMessagesSpec); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index f1e39629c..904c3d112 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -98,6 +98,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; @@ -108,6 +109,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PCalendarService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PCannedRepliesService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PTrackService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PDataDictionarySyncService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest; @@ -865,6 +867,12 @@ public class HuaweiSupportProvider { trackService.register(); } } + if (getHuaweiCoordinator().supportsCannedReplies()) { + if (HuaweiP2PCannedRepliesService.getRegisteredInstance(huaweiP2PManager) == null) { + HuaweiP2PCannedRepliesService cannedRepliesService = new HuaweiP2PCannedRepliesService(huaweiP2PManager); + cannedRepliesService.register(); + } + } if (HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager) == null) { HuaweiP2PDataDictionarySyncService trackService = new HuaweiP2PDataDictionarySyncService(huaweiP2PManager); trackService.register(); @@ -2514,4 +2522,23 @@ public class HuaweiSupportProvider { public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) { getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName, musicIds); } + + public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) { + if (cannedMessagesSpec.type != CannedMessagesSpec.TYPE_GENERIC) { + LOG.warn("Got unsupported canned messages type: {}", cannedMessagesSpec.type); + return; + } + + if(cannedMessagesSpec.cannedMessages.length == 0) { + GB.toast(context, HuaweiSupportProvider.this.getContext().getString(R.string.canned_replies_not_empty), Toast.LENGTH_SHORT, GB.WARN); + LOG.warn(HuaweiSupportProvider.this.getContext().getString(R.string.canned_replies_not_empty)); + } + + HuaweiP2PCannedRepliesService cannedRepliesService = HuaweiP2PCannedRepliesService.getRegisteredInstance(huaweiP2PManager); + if(cannedRepliesService == null) { + LOG.warn("P2P canned replies service is not registered"); + return; + } + cannedRepliesService.sendReplies(cannedMessagesSpec.cannedMessages); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java index a4b40f840..d66be7dd3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java @@ -91,14 +91,14 @@ public abstract class HuaweiBaseP2PService { } public void handlePacket(P2P.P2PCommand.Response packet) { - LOG.info("HuaweiP2PCalendarService handlePacket: {} Code: {}", packet.cmdId, packet.respCode); + LOG.info("HuaweiBaseP2PService handlePacket: {} Code: {}", packet.cmdId, packet.respCode); if (waitPackets.containsKey(packet.sequenceId)) { - LOG.info("HuaweiP2PCalendarService handlePacket find handler"); + LOG.info("HuaweiBaseP2PService handlePacket find handler"); HuaweiP2PCallback handle = waitPackets.remove(packet.sequenceId); if(handle != null) { handle.onResponse(packet.respCode, packet.respData); } else { - LOG.error("HuaweiP2PCalendarService handler is null"); + LOG.error("HuaweiBaseP2PService handler is null"); } } else { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCannedRepliesService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCannedRepliesService.java new file mode 100644 index 000000000..1c887021d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCannedRepliesService.java @@ -0,0 +1,138 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p; + +import android.text.TextUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager; + +public class HuaweiP2PCannedRepliesService extends HuaweiBaseP2PService { + private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PCannedRepliesService.class); + private final AtomicBoolean isRegistered = new AtomicBoolean(false); + + public static final String MODULE = "hw.unitedevice.smsquick"; + + + public static final int CMD_CANNED_REPLY_QUERY = 7001; + public static final int CMD_CANNED_REPLY_UPDATE = 7002; + public static final int CMD_CANNED_REPLY_CONNECT = 7003; + + public HuaweiP2PCannedRepliesService(HuaweiP2PManager manager) { + super(manager); + LOG.info("HuaweiP2PCannedRepliesService"); + } + + public static HuaweiP2PCannedRepliesService getRegisteredInstance(HuaweiP2PManager manager) { + return (HuaweiP2PCannedRepliesService) manager.getRegisteredService(HuaweiP2PCannedRepliesService.MODULE); + } + + @Override + public String getModule() { + return HuaweiP2PCannedRepliesService.MODULE; + } + + @Override + public String getPackage() { + return "com.huawei.watch.home"; + } + + @Override + public String getFingerprint() { + return "603AC6A57E2023E00C9C93BB539CA653DF3003EBA4E92EA1904BA4AAA5D938F0"; + } + + @Override + public void registered() { + isRegistered.set(true); + // NOTE: sendConnect can clean saved canned messages. Additional research required + //sendConnect(); + sendQuery(); + } + + @Override + public void unregister() { + isRegistered.set(false); + + } + + public void sendConnect() { + HuaweiTLV tlv = new HuaweiTLV(); + tlv.put(0x1, CMD_CANNED_REPLY_CONNECT); + sendCommand(tlv.serialize(), null); + } + + public void sendQuery() { + HuaweiTLV tlv = new HuaweiTLV(); + tlv.put(0x1, CMD_CANNED_REPLY_QUERY); + sendCommand(tlv.serialize(), null); + } + + public void sendReplies(String[] replies) { + HuaweiTLV tlv = new HuaweiTLV(); + for (String reply : replies) { + tlv.put(0x83, new HuaweiTLV().put(0x04, reply)); + } + HuaweiTLV res = new HuaweiTLV(); + res.put(0x1, CMD_CANNED_REPLY_UPDATE).put(0x82, tlv); + + sendCommand(res.serialize(), null); + } + + private void parseDeviceReplies(HuaweiTLV tlv) { + List replies = tlv.getObjects(0x83); + if (replies.isEmpty()) + return; + final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences(); + + for (int i = 1; i <= manager.getSupportProvider().getHuaweiCoordinator().getCannedRepliesSlotCount(manager.getSupportProvider().getDevice()); i++) { + String message = null; + if (replies.size() >= i) { + if (replies.get(i - 1).contains(0x04)) { + try { + String reply = replies.get(i - 1).getString(0x04); + if (!TextUtils.isEmpty(reply)) { + message = reply; + } + } catch (HuaweiPacket.MissingTagException e) { + LOG.info("No tag"); + } + } + } + gbDeviceEventUpdatePreferences.withPreference("canned_reply_" + i, message); + } + + manager.getSupportProvider().evaluateGBDeviceEvent(gbDeviceEventUpdatePreferences); + } + + @Override + public void handleData(byte[] data) { + LOG.info("HuaweiP2PCannedRepliesService handleData"); + try { + HuaweiTLV tlv = new HuaweiTLV(); + tlv.parse(data); + LOG.error(tlv.toString()); + if (tlv.contains(0x01)) { + int code = tlv.getInteger(0x01); + if (code == CMD_CANNED_REPLY_CONNECT) { + // send default replies, replies cannot be empty + String[] replies = {"OK", "Yes", "No"}; + sendReplies(replies); + } + if (code == CMD_CANNED_REPLY_QUERY) { + if (tlv.contains(0x82)) { + parseDeviceReplies(tlv.getObject(0x82)); + } + } + } + } catch (HuaweiPacket.MissingTagException e) { + throw new RuntimeException(e); + } + } +} \ 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 9e9bcc178..fdc84b519 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3579,4 +3579,5 @@ Minimum battery charge in % When enabled, the battery can be charged while discharging Allow battery pass-through + There should be at least one canned reply.