Huawei: Allow to set and edit canned replies

This commit is contained in:
Me7c7 2025-01-04 12:26:21 +02:00 committed by José Rebelo
parent 8da2b68eed
commit 1450219351
9 changed files with 208 additions and 4 deletions

View File

@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
@ -153,6 +154,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
return huaweiCoordinator.getContactsSlotCount(device); return huaweiCoordinator.getContactsSlotCount(device);
} }
@Override
public int getCannedRepliesSlotCount(GBDevice device) {
return huaweiCoordinator.getCannedRepliesSlotCount(device);
}
@Override @Override
public boolean supportsCalendarEvents() { public boolean supportsCalendarEvents() {
return huaweiCoordinator.supportsCalendarEvents(); return huaweiCoordinator.supportsCalendarEvents();

View File

@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
@ -285,6 +286,9 @@ public class HuaweiCoordinator {
if (supportsNotificationsRepeatedNotify() || supportsNotificationsRemoveSingle()){ if (supportsNotificationsRepeatedNotify() || supportsNotificationsRemoveSingle()){
notifications.add(R.xml.devicesettings_autoremove_notifications); notifications.add(R.xml.devicesettings_autoremove_notifications);
} }
if (getCannedRepliesSlotCount(device) > 0) {
notifications.add(R.xml.devicesettings_canned_reply_16);
}
if (supportsNotificationOnBluetoothLoss()) if (supportsNotificationOnBluetoothLoss())
notifications.add(R.xml.devicesettings_disconnectnotification_noshed); notifications.add(R.xml.devicesettings_disconnectnotification_noshed);
if (supportsDoNotDisturb(device)) if (supportsDoNotDisturb(device))
@ -681,13 +685,18 @@ public class HuaweiCoordinator {
return supportsExpandCapability(89); return supportsExpandCapability(89);
return false; return false;
} }
public boolean supportsNotificationsRemoveSingle() { public boolean supportsNotificationsRemoveSingle() {
if (supportsExpandCapability()) if (supportsExpandCapability())
return supportsExpandCapability(120); return supportsExpandCapability(120);
return false; return false;
} }
public boolean supportsCannedReplies() {
if (supportsExpandCapability())
return supportsExpandCapability(82);
return false;
}
public boolean supportsPromptPushMessage () { public boolean supportsPromptPushMessage () {
// do not ask for capabilities under specific condition // do not ask for capabilities under specific condition
@ -758,6 +767,11 @@ public class HuaweiCoordinator {
return supportsContacts()?maxContactsCount:0; return supportsContacts()?maxContactsCount:0;
} }
public int getCannedRepliesSlotCount(GBDevice device) {
// TODO: find proper count
return supportsCannedReplies()?10:0;
}
public void setTransactionCrypted(boolean crypted) { public void setTransactionCrypted(boolean crypted) {
this.transactionCrypted = crypted; this.transactionCrypted = crypted;
} }

View File

@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
@ -162,6 +163,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
return huaweiCoordinator.getContactsSlotCount(device); return huaweiCoordinator.getContactsSlotCount(device);
} }
@Override
public int getCannedRepliesSlotCount(GBDevice device) {
return huaweiCoordinator.getCannedRepliesSlotCount(device);
}
@Override @Override
public boolean supportsCalendarEvents() { public boolean supportsCalendarEvents() {
return huaweiCoordinator.supportsCalendarEvents(); return huaweiCoordinator.supportsCalendarEvents();

View File

@ -31,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; 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<Integer> musicIds) { public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds); supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
} }
@Override
public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
supportProvider.onSetCannedMessages(cannedMessagesSpec);
}
} }

View File

@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; 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<Integer> musicIds) { public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds); supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
} }
@Override
public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
supportProvider.onSetCannedMessages(cannedMessagesSpec);
}
} }

View File

@ -98,6 +98,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate; import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; 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.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PCalendarService; 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.HuaweiP2PTrackService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PDataDictionarySyncService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PDataDictionarySyncService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest;
@ -865,6 +867,12 @@ public class HuaweiSupportProvider {
trackService.register(); trackService.register();
} }
} }
if (getHuaweiCoordinator().supportsCannedReplies()) {
if (HuaweiP2PCannedRepliesService.getRegisteredInstance(huaweiP2PManager) == null) {
HuaweiP2PCannedRepliesService cannedRepliesService = new HuaweiP2PCannedRepliesService(huaweiP2PManager);
cannedRepliesService.register();
}
}
if (HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager) == null) { if (HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager) == null) {
HuaweiP2PDataDictionarySyncService trackService = new HuaweiP2PDataDictionarySyncService(huaweiP2PManager); HuaweiP2PDataDictionarySyncService trackService = new HuaweiP2PDataDictionarySyncService(huaweiP2PManager);
trackService.register(); trackService.register();
@ -2514,4 +2522,23 @@ public class HuaweiSupportProvider {
public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) { public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName, 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);
}
} }

View File

@ -91,14 +91,14 @@ public abstract class HuaweiBaseP2PService {
} }
public void handlePacket(P2P.P2PCommand.Response packet) { 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)) { if (waitPackets.containsKey(packet.sequenceId)) {
LOG.info("HuaweiP2PCalendarService handlePacket find handler"); LOG.info("HuaweiBaseP2PService handlePacket find handler");
HuaweiP2PCallback handle = waitPackets.remove(packet.sequenceId); HuaweiP2PCallback handle = waitPackets.remove(packet.sequenceId);
if(handle != null) { if(handle != null) {
handle.onResponse(packet.respCode, packet.respData); handle.onResponse(packet.respCode, packet.respData);
} else { } else {
LOG.error("HuaweiP2PCalendarService handler is null"); LOG.error("HuaweiBaseP2PService handler is null");
} }
} else { } else {

View File

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

View File

@ -3579,4 +3579,5 @@
<string name="battery_minimum_charge">Minimum battery charge in %</string> <string name="battery_minimum_charge">Minimum battery charge in %</string>
<string name="battery_allow_pass_though_summary">When enabled, the battery can be charged while discharging</string> <string name="battery_allow_pass_though_summary">When enabled, the battery can be charged while discharging</string>
<string name="battery_allow_pass_through">Allow battery pass-through</string> <string name="battery_allow_pass_through">Allow battery pass-through</string>
<string name="canned_replies_not_empty">There should be at least one canned reply.</string>
</resources> </resources>