diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportCoordinator.java index 726259a517..ea8a9f16e9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportCoordinator.java @@ -1,16 +1,20 @@ package nodomain.freeyourgadget.gadgetbridge.devices.igpsport; +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + import androidx.annotation.NonNull; import java.util.regex.Pattern; -import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; -import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport.IGPSportDeviceSupport; @@ -63,4 +67,32 @@ public class IGPSportCoordinator extends AbstractBLEDeviceCoordinator { return true; } + @Override + public boolean supportsAppsManagement(final GBDevice device) { + return true; + } + + @Override + public Class getAppsManagementActivity() { + return AppManagerActivity.class; + } + + + @Override + public InstallHandler findInstallHandler(final Uri uri, final Context context) { + +// final IGPSportAgpsInstallHandler agpsInstallHandler = new IGPSportAgpsInstallHandler(uri, context); +// if (agpsInstallHandler.isValid()) { +// return agpsInstallHandler; +// } + + final IGPSportRouteInstallHandler routeInstallHandler = new IGPSportRouteInstallHandler(uri, context); + if (routeInstallHandler.isValid()) { + return routeInstallHandler; + } + + return null; + } + + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportRouteInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportRouteInstallHandler.java new file mode 100644 index 0000000000..da35fce0d0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/igpsport/IGPSportRouteInstallHandler.java @@ -0,0 +1,145 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.igpsport; + +import android.content.Context; +import android.net.Uri; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; + + +public class IGPSportRouteInstallHandler implements InstallHandler { + + private static final Logger LOG = LoggerFactory.getLogger(IGPSportRouteInstallHandler.class); + + protected final Context mContext; + private byte[] rawBytes = null; + private String filename; + private String extension; + private List routeExtensions = Arrays.asList("cnx", "gpx", "fit", "tcx", "xml"); + + public IGPSportRouteInstallHandler(final Uri uri, final Context context) { + this.mContext = context; + + final UriHelper uriHelper; + try { + uriHelper = UriHelper.get(uri, context); + filename = uriHelper.getFileName(); + int strLength = filename.lastIndexOf("."); + if(strLength > 0) + extension = filename.substring(strLength + 1).toLowerCase(); + } catch (final IOException e) { + LOG.error("Failed to get uri", e); + return; + } + + try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) { + rawBytes = FileUtils.readAll(in, 1024 * 1024); // 1MB + + try (ByteArrayInputStream bais = new ByteArrayInputStream(rawBytes)) { + + } catch (final IOException e) { + LOG.error("Failed to read xml", e); + } + + } catch (final Exception e) { + LOG.error("Failed to read file", e); + } + } + + @Override + public boolean isValid() { + if (routeExtensions.contains(extension)) + return true; + else + return false; + } + + @Override + public void validateInstallation(InstallActivity installActivity, GBDevice device) { + if (device.isBusy()) { + installActivity.setInfoText(device.getBusyTask()); + installActivity.setInstallEnabled(false); + return; + } + + final DeviceCoordinator coordinator = device.getDeviceCoordinator(); + if (!(coordinator instanceof IGPSportCoordinator)) { + LOG.warn("Coordinator is not a IGPSportCoordinator: {}", coordinator.getClass()); + installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported)); + installActivity.setInstallEnabled(false); + return; + } + + if (!device.isInitialized()) { + installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready)); + installActivity.setInstallEnabled(false); + return; + } + + final GenericItem fwItem = createInstallItem(device); + fwItem.setIcon(coordinator.getDefaultIconResource()); + + if (rawBytes == null) { + fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version)); + installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device)); + installActivity.setInstallEnabled(false); + return; + } + + final StringBuilder builder = new StringBuilder(); + final String cnxRoute = mContext.getString(R.string.kind_gpx_route); + builder.append(mContext.getString(R.string.fw_upgrade_notice, filename)); + installActivity.setInfoText(builder.toString()); + installActivity.setInstallItem(fwItem); + installActivity.setInstallEnabled(true); + + } + + @Override + public void onStartInstall(GBDevice device) { + + } + + private GenericItem createInstallItem(final GBDevice device) { + DeviceCoordinator coordinator = device.getDeviceCoordinator(); + final String firmwareName = mContext.getString( + R.string.installhandler_firmware_name, + mContext.getString(coordinator.getDeviceNameResource()), + mContext.getString(R.string.kind_gpx_route), + "test" //FIXME + ); + return new GenericItem(firmwareName); + } + + public byte[] getBytes() { + return rawBytes; + } + + public int getSize() { + return rawBytes.length; + } + + public String getFilename() { + return filename; + } + + public String getExtension() { + return extension; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java index ab2f159da8..7d15707161 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java @@ -4,6 +4,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Intent; +import android.net.Uri; import android.widget.Toast; import com.google.protobuf.ByteString; @@ -14,17 +15,18 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Calendar; +import java.util.Random; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.devices.igpsport.IGPSportRouteInstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.igpsport.IGPSportConstants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; @@ -35,7 +37,9 @@ import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Ble; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Config; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.CyclingData; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Firmware; +import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.GeneralFileOperation; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Ins; +import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.RoutePlan; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; @@ -56,6 +60,7 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { public BluetoothGattCharacteristic readCharacteristic; public BluetoothGattCharacteristic writeCharacteristic; public BluetoothGattCharacteristic writeCharacteristicThird; + public BluetoothGattCharacteristic writeCharacteristicFourth; public final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo(); public final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); public final DeviceInfoProfile deviceInfoProfile; @@ -128,6 +133,7 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { readCharacteristic = getCharacteristic(IGPSportConstants.UUID_IGPSPORT_CHARACTERISTIC_FIRST_RX); writeCharacteristic = getCharacteristic(IGPSportConstants.UUID_IGPSPORT_CHARACTERISTIC_FIRST_TX); writeCharacteristicThird = getCharacteristic(IGPSportConstants.UUID_IGPSPORT_CHARACTERISTIC_THIRD_TX); + writeCharacteristicFourth = getCharacteristic(IGPSportConstants.UUID_IGPSPORT_CHARACTERISTIC_FOURTH_TX); builder.notify(getCharacteristic(IGPSportConstants.UUID_IGPSPORT_CHARACTERISTIC_FIRST_RX), true); builder.notify(getCharacteristic(IGPSportConstants.UUID_IGPSPORT_CHARACTERISTIC_SECOND_RX), true); @@ -187,31 +193,44 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info("Characteristic changed value: " + GB.hexdump(characteristic.getValue())); byte[] data = characteristic.getValue(); - if (data[0] != IGPSportConstants.DATA_HEADER) { - LOG.info("FitPro, packet not starting with 0x01: " + data[0] + "other message types not implemented yet"); - return false; + if (data[0] == IGPSportConstants.DATA_HEADER) { + + if (data != null && data.length > 20) { + byte mainService = data[1]; + byte mainOperation = data[4]; + int dataSize = ByteBuffer.wrap(data, 7, 2).getShort(); + + byte[] pbData = new byte[dataSize]; + System.arraycopy(data, 20, pbData, 0, dataSize); + + try { + switch (mainService) { + case Common.service_type_index.enum_SERVICE_TYPE_INDEX_FACTORY_VALUE: + handleFactoryData(pbData); + break; + case Common.service_type_index.enum_SERVICE_TYPE_INDEX_FIRMWARE_VALUE: + handleFirmwareData(pbData); + break; + } + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + + } } - if (data != null && data.length > 20) { + + if (data[0] == 0x02) { + //0215FFFF03FFFF00FFFFFFFFFFFFFFFFFFFFFF55 byte mainService = data[1]; byte mainOperation = data[4]; - int dataSize = ByteBuffer.wrap(data, 7,2).getShort(); - - byte[] pbData = new byte[dataSize]; - System.arraycopy(data, 20, pbData, 0, dataSize); - - try { - switch (mainService) { - case Common.service_type_index.enum_SERVICE_TYPE_INDEX_FACTORY_VALUE: - handleFactoryData(pbData); - break; - case Common.service_type_index.enum_SERVICE_TYPE_INDEX_FIRMWARE_VALUE: - handleFirmwareData(pbData); - break; - } - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); + byte result = data[7]; + switch (mainService) { + case Common.service_type_index.enum_SERVICE_TYPE_INDEX_ROUTE_PLAN_VALUE: + if(mainOperation == Common.SERVICE_OPERATE_TYPE.enum_SERVICE_OPERATE_TYPE_ADD_VALUE) { + gbDevice.unsetBusyTask(); + } + break; } - } return false; @@ -432,4 +451,58 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { } + + @Override + public void onInstallApp(Uri uri) { + //final IGPSportGpxRouteInstallHandler gpxRouteHandler = new IGPSportGpxRouteInstallHandler(uri, getContext()); + final IGPSportRouteInstallHandler routeHandler = new IGPSportRouteInstallHandler(uri, getContext()); + if (routeHandler.isValid()) { + try { + TransactionBuilder builder = performInitialized("prepare upload gpx"); + +// RoutePlan.route_plan_data_msg.Builder routePlanBuilder = RoutePlan.route_plan_data_msg.newBuilder(); +// routePlanBuilder.setServiceType(Common.service_type_index.enum_SERVICE_TYPE_INDEX_ROUTE_PLAN); +// routePlanBuilder.setRoutePlanOperateType(RoutePlan.ROUTE_PLAN_OPERATE_TYPE.enum_ROUTE_PLAN_OPERATE_TYPE_LIST_NUM_GET); +// byte[] routePlanBytes = craftData(routePlanBuilder.getServiceType().getNumber(), 0xff, routePlanBuilder.getRoutePlanOperateType().getNumber(), routePlanBuilder.build().toByteArray()); +// builder.write(writeCharacteristicFourth, routePlanBytes); +// builder.wait(1000); +// +// RoutePlan.route_plan_data_msg.Builder routePlan2ndBuilder = RoutePlan.route_plan_data_msg.newBuilder(); +// routePlan2ndBuilder.setServiceType(Common.service_type_index.enum_SERVICE_TYPE_INDEX_ROUTE_PLAN); +// routePlan2ndBuilder.setRoutePlanOperateType(RoutePlan.ROUTE_PLAN_OPERATE_TYPE.enum_ROUTE_PLAN_OPERATE_TYPE_LIST_GET); +// routePlan2ndBuilder.setRouteListGetMsg(Common.file_list_get_message.newBuilder().setFileIndexEnd(0).setFileIndexEnd(0)); +// byte[] routePlan2ndBytes = craftData(routePlan2ndBuilder.getServiceType().getNumber(), 0xff, routePlan2ndBuilder.getRoutePlanOperateType().getNumber(), routePlan2ndBuilder.build().toByteArray()); +// builder.write(writeCharacteristicFourth, routePlan2ndBytes); +// builder.wait(1000); +// +// builder.write(writeCharacteristicFourth, routePlanBytes); +// builder.wait(1000); + + Random random = new Random(); + int ran = random.nextInt(); + GeneralFileOperation.general_file_operation.Builder fileOperationbuilder = GeneralFileOperation.general_file_operation.newBuilder(); + fileOperationbuilder.setServiceType(Common.service_type_index.enum_SERVICE_TYPE_INDEX_FILE_OPERATION) + .setOperateType(Common.SERVICE_OPERATE_TYPE.enum_SERVICE_OPERATE_TYPE_ADD) + .setFileType(GeneralFileOperation.file_operation_type.enum_FILE_TYPE_ROUTE_PLAN) + .setFileId(ran) + .setFileExtension(routeHandler.getExtension()) + .setFileName(routeHandler.getFilename()) + .setFileSize(routeHandler.getSize()); + + byte[] fileOperationBytes = craftFileData(fileOperationbuilder.getServiceType().getNumber(), + 0xff, + fileOperationbuilder.getOperateType().getNumber(), + fileOperationbuilder.build().toByteArray(), + routeHandler.getBytes()); + builder.writeChunkedData(writeCharacteristicFourth, fileOperationBytes, getMTU()); + gbDevice.setBusyTask("Installing route"); + builder.queue(getQueue()); + + } catch (final Exception e) { + GB.toast(getContext(), "Gpx install error: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); + } + + return; + } + } }