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

Compare commits

..

13 Commits

Author SHA1 Message Date
kuhy
85178d8d40 Garmin protocol: show AGPS data status in settings 2024-05-03 16:41:01 +02:00
kuhy
b92e1ff947 Garmin protocol: add AGPS data checks 2024-05-03 16:41:01 +02:00
kuhy
00faf1be6b Garmin protocol: install AGPS data as firmware 2024-05-03 16:40:59 +02:00
kuhy
e97d699c6f Garmin protocol: improve detection of successfully sent files (DataTransferHandler) 2024-05-03 16:38:37 +02:00
kuhy
13cf48a6c5 Garmin protocol: add support for AGPS data retrieval 2024-05-03 16:38:37 +02:00
Daniele Gobbetti
2bfbb75c0b Fixup: Introduce device specific writable directory (MAC address)
Add logic to not fetch again files which had the previously defined name
2024-05-03 12:22:15 +02:00
Daniele Gobbetti
598549b1f5 Introduce device specific writable directory (MAC address)
Also adds temporary method to move the fetched files from the legacy path to the new one which does not include the device name.
Also moves the FileIndex to the end of the cached files to allow for easier sorting.

Cherry-picked from 525b395c01 and adapted
2024-05-03 10:27:00 +02:00
José Rebelo
7dcefd1815 Garmin: Make fit header crc optional 2024-05-03 09:51:44 +02:00
Daniele Gobbetti
f932dabc72 Garmin: enable unicode Emoji for all devices
This seems to be widely supported by garmin devices, hence enable it in the base coordinator. Specific devices not supporting Unicode Emojis can override this method and return false.
2024-05-03 09:45:04 +02:00
Daniele Gobbetti
36911b890f Garmin: harmonize device names
All device name strings start with manufacturer name.
Normalized the usage of accented i.
2024-05-03 09:36:36 +02:00
Andreas Schneider
1a07ad8ff1 Garmin: add coordinator for Instinct Crossover 2024-05-03 09:34:34 +02:00
Daniele Gobbetti
19095caa6e Garmin: fix regression in call handling
Add a fictitious action to the notification to enable reply/hangup/reject from the watch.
Also fixes the behavior on sms reply, which should also reject the incoming call.

Change the log level in case some of the canned messages types are left as default to info, as this is a supported scenario.
2024-05-03 09:30:38 +02:00
Daniele Gobbetti
533ce0441f Garmin: encode unknown weather codes as invalid 2024-05-01 16:51:56 +02:00
13 changed files with 116 additions and 26 deletions

View File

@ -79,6 +79,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -290,6 +291,18 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return null;
}
@Override
public File getWritableExportDirectory(final GBDevice device) throws IOException {
File dir;
dir = new File(FileUtils.getExternalFilesDir() + File.separator + device.getAddress());
if (!dir.isDirectory()) {
if (!dir.mkdir()) {
throw new IOException("Cannot create device specific directory for " + device.getName());
}
}
return dir;
}
@Override
public String getAppCacheSortFilename() {
return null;

View File

@ -442,6 +442,11 @@ public interface DeviceCoordinator {
*/
File getAppCacheDir() throws IOException;
/**
* Returns the dedicated writable export directory for this device.
*/
File getWritableExportDirectory(GBDevice device) throws IOException;
/**
* Returns a String containing the device app sort order filename.
*/

View File

@ -101,6 +101,12 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
}
@Override
public boolean supportsUnicodeEmojis() {
return true;
}
@Override
public InstallHandler findInstallHandler(final Uri uri, final Context context) {
if (supportsAgpsUpdates()) {
final GarminAgpsInstallHandler agpsInstallHandler = new GarminAgpsInstallHandler(uri, context);

View File

@ -0,0 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinctcrossover;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import java.util.regex.Pattern;
public class GarminInstinctCrossoverCoordinator extends GarminCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Instinct Crossover");
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_garmin_instinct_crossover;
}
}

View File

@ -54,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinctsolar.GarminI
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2s.GarminInstinct2SCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2solar.GarminInstinct2SolarCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2soltac.GarminInstinct2SolTacCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinctcrossover.GarminInstinctCrossoverCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.venu3.GarminVenu3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.vivoactive4s.GarminVivoActive4SCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.vivoactive5.GarminVivoActive5Coordinator;
@ -335,6 +336,7 @@ public enum DeviceType {
GARMIN_INSTINCT_2S(GarminInstinct2SCoordinator.class),
GARMIN_INSTINCT_2_SOLAR(GarminInstinct2SolarCoordinator.class),
GARMIN_INSTINCT_2_SOLTAC(GarminInstinct2SolTacCoordinator.class),
GARMIN_INSTINCT_CROSSOVER(GarminInstinctCrossoverCoordinator.class),
GARMIN_VIVOMOVE_STYLE(GarminVivomoveStyleCoordinator.class),
GARMIN_VENU_3(GarminVenu3Coordinator.class),
GARMIN_VIVOACTIVE_4S(GarminVivoActive4SCoordinator.class),

View File

@ -336,6 +336,12 @@ public class FileTransferHandler implements MessageHandler {
}
public String getFileName() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String dateString = dateFormat.format(fileDate);
return getFiletype().name() + "_" + dateString + "_" + getFileIndex() + (getFiletype().isFitFile() ? ".fit" : ".bin");
}
public String getLegacyFileName() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String dateString = dateFormat.format(fileDate);
return getFiletype().name() + "_" + getFileIndex() + "_" + dateString + (getFiletype().isFitFile() ? ".fit" : ".bin");

View File

@ -452,6 +452,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
}
private void processDownloadQueue() {
moveFilesFromLegacyCache(); //TODO: remove before merging
if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
if (!gbDevice.isBusy()) {
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
@ -461,7 +464,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
try {
FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
while (checkFileExists(directoryEntry.getFileName())) {
while (checkFileExists(directoryEntry.getFileName()) || checkFileExists(directoryEntry.getLegacyFileName())) {
LOG.debug("File: {} already downloaded, not downloading again.", directoryEntry.getFileName());
if (!getKeepActivityDataOnDevice()) // delete file from watch if already downloaded
sendOutgoingMessage(new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
@ -491,6 +494,36 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
}
}
private void moveFilesFromLegacyCache() { //TODO: remove before merging
File legacyDir;
try {
legacyDir = new File(FileUtils.getExternalFilesDir() + "/" + FileUtils.makeValidFileName(getDevice().getName() + "_" + getDevice().getAddress()));
if (legacyDir.isDirectory()) {
final File newDir = getWritableExportDirectory();
File[] files = legacyDir.listFiles();
for (File file : files) {
if (file.isFile()) {
File destFile = new File(newDir, file.getName());
boolean success = file.renameTo(destFile);
if (!success) {
LOG.error("Failed to move file {}", file.getName());
} else {
LOG.info("Moved file {} to new cache directory", file.getName());
}
}
}
boolean removed = legacyDir.delete();
if (!removed) {
LOG.error("Failed to remove legacy directory: {}", legacyDir);
}
}
} catch (IOException e) {
LOG.error(e.getMessage());
}
}
private void enableBatteryLevelUpdate() {
final ProtobufMessage batteryLevelProtobufRequest = protocolBufferHandler.prepareProtobufRequest(GdiSmartProto.Smart.newBuilder()
.setDeviceStatusService(
@ -626,14 +659,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
}
public File getWritableExportDirectory() throws IOException {
File dir;
dir = new File(FileUtils.getExternalFilesDir() + "/" + FileUtils.makeValidFileName(getDevice().getName() + "_" + getDevice().getAddress()));
if (!dir.isDirectory()) {
if (!dir.mkdir()) {
throw new IOException("Cannot create device specific directory for " + getDevice().getName());
}
}
return dir;
return getDevice().getDeviceCoordinator().getWritableExportDirectory(getDevice());
}
@Override

View File

@ -84,6 +84,11 @@ public class NotificationsHandler implements MessageHandler {
callNotificationSpec.type = NotificationType.GENERIC_PHONE;
callNotificationSpec.body = StringUtils.isEmpty(callSpec.name) ? callSpec.number : callSpec.name;
// add an empty bogus action to toggle the hasActions boolean. The actions are hardcoded on the watch in case of incoming calls.
callNotificationSpec.attachedActions = new ArrayList<>();
callNotificationSpec.attachedActions.add(0, new NotificationSpec.Action());
return onNotification(callNotificationSpec);
} else {
if (callSpec.number != null) // this happens in debug screen
@ -180,6 +185,8 @@ public class NotificationsHandler implements MessageHandler {
final GBDeviceEventCallControl deviceEvtCallControl = new GBDeviceEventCallControl();
switch (message.getNotificationAction()) {
case REPLY_INCOMING_CALL:
deviceEvtCallControl.event = GBDeviceEventCallControl.Event.REJECT;
message.addGbDeviceEvent(deviceEvtCallControl);
case REPLY_MESSAGES:
deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
deviceEvtNotificationControl.reply = message.getActionString();
@ -188,23 +195,23 @@ public class NotificationsHandler implements MessageHandler {
} else {
deviceEvtNotificationControl.handle = mNotificationReplyAction.lookup(notificationSpec.getId()); //handle of wearable action is needed
}
message.setDeviceEvent(deviceEvtNotificationControl);
message.addGbDeviceEvent(deviceEvtNotificationControl);
break;
case ACCEPT_INCOMING_CALL:
deviceEvtCallControl.event = GBDeviceEventCallControl.Event.ACCEPT;
message.setDeviceEvent(deviceEvtCallControl);
message.addGbDeviceEvent(deviceEvtCallControl);
break;
case REJECT_INCOMING_CALL:
deviceEvtCallControl.event = GBDeviceEventCallControl.Event.REJECT;
message.setDeviceEvent(deviceEvtCallControl);
message.addGbDeviceEvent(deviceEvtCallControl);
break;
case DISMISS_NOTIFICATION:
deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
message.setDeviceEvent(deviceEvtNotificationControl);
message.addGbDeviceEvent(deviceEvtNotificationControl);
break;
case BLOCK_APPLICATION:
deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE;
message.setDeviceEvent(deviceEvtNotificationControl);
message.addGbDeviceEvent(deviceEvtNotificationControl);
break;
}
}

View File

@ -365,7 +365,7 @@ public class ProtocolBufferHandler implements MessageHandler {
);
} else {
builder.setStatus(GdiSmsNotification.SmsNotificationService.ResponseStatus.GENERIC_ERROR);
LOG.error("Missing canned messages data for type {}", requestedType);
LOG.info("Missing canned messages data for type {}", requestedType);
}
}

View File

@ -192,7 +192,7 @@ public class FitFile {
if (hasCRC) {
int incomingCrc = garminByteBufferReader.readShort();
if (incomingCrc != ChecksumCalculator.computeCrc(garminByteBufferReader.asReadOnlyBuffer(), 0, headerSize - 2)) {
if (incomingCrc != 0 && incomingCrc != ChecksumCalculator.computeCrc(garminByteBufferReader.asReadOnlyBuffer(), 0, headerSize - 2)) {
throw new IllegalArgumentException("Wrong CRC for header in FIT file");
}
// LOG.info("Fit File Header didn't have CRC, no check performed.");

View File

@ -138,7 +138,7 @@ public class FieldDefinitionWeatherCondition extends FieldDefinition {
case 902: //hurricane
case 962: //hurricane
default:
throw new IllegalArgumentException("Unknown weather code " + openWeatherCode);
return 255; //invalid
}
}

View File

@ -1,5 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@ -21,7 +22,7 @@ public class NotificationControlMessage extends GFDIMessage {
private NotificationsHandler.LegacyNotificationAction legacyNotificationAction;
private NotificationsHandler.NotificationAction notificationAction;
private String actionString;
private GBDeviceEvent deviceEvent;
private List<GBDeviceEvent> gbDeviceEventList;
public NotificationControlMessage(GarminMessage garminMessage, NotificationsHandler.NotificationCommand command, int notificationId, NotificationsHandler.NotificationAction notificationAction, String actionString) {
this.garminMessage = garminMessage;
@ -108,13 +109,17 @@ public class NotificationControlMessage extends GFDIMessage {
return actionString;
}
public void setDeviceEvent(GBDeviceEvent deviceEvent) {
this.deviceEvent = deviceEvent;
public void addGbDeviceEvent(GBDeviceEvent gbDeviceEvent) {
if (null == this.gbDeviceEventList)
this.gbDeviceEventList = new ArrayList<>();
this.gbDeviceEventList.add(gbDeviceEvent);
}
@Override
public List<GBDeviceEvent> getGBDeviceEvent() {
return Collections.singletonList(deviceEvent);
if (null == this.gbDeviceEventList)
return Collections.emptyList();
return gbDeviceEventList;
}
public NotificationsHandler.LegacyNotificationAction getLegacyNotificationAction() {

View File

@ -1486,15 +1486,16 @@
<string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
<string name="devicetype_amazfit_x">Amazfit X</string>
<string name="devicetype_zepp_e">Zepp E</string>
<string name="devicetype_garmin_vivomove_hr">Garmin Vivomove HR</string>
<string name="devicetype_garmin_vivomove_style">Vívomove Style</string>
<string name="devicetype_garmin_vivomove_hr">Garmin Vívomove HR</string>
<string name="devicetype_garmin_vivomove_style">Garmin Vívomove Style</string>
<string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
<string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
<string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
<string name="devicetype_garmin_instinct_2_soltac">Instinct 2 SolTac</string>
<string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
<string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
<string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
<string name="devicetype_garmin_vivoactive_4s">Vívoactive 4S</string>
<string name="devicetype_garmin_vivoactive_5">Vivoactive 5</string>
<string name="devicetype_garmin_vivoactive_4s">Garmin Vívoactive 4S</string>
<string name="devicetype_garmin_vivoactive_5">Garmin Vívoactive 5</string>
<string name="devicetype_vibratissimo">Vibratissimo</string>
<string name="devicetype_um25">UM-25</string>
<string name="devicetype_liveview">LiveView</string>