diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miwatch/MiWatchLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java similarity index 90% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miwatch/MiWatchLiteCoordinator.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java index 8327d9b90..51a9b17e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miwatch/MiWatchLiteCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java @@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.devices.miwatch; +package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatch; import android.content.Context; import android.net.Uri; @@ -28,8 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.miwatch.MiWatchLiteSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPlaintextSupport; public class MiWatchLiteCoordinator extends XiaomiCoordinator { @Override @@ -62,6 +61,6 @@ public class MiWatchLiteCoordinator extends XiaomiCoordinator { @NonNull @Override public Class getDeviceSupportClass() { - return MiWatchLiteSupport.class; + return XiaomiPlaintextSupport.class; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index 3d89a8c8d..53f026eaa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -143,7 +143,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsStee import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8.MiBand8Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator; -import nodomain.freeyourgadget.gadgetbridge.devices.miwatch.MiWatchLiteCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatch.MiWatchLiteCoordinator; /** * For every supported device, a device type constant must exist. diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miwatch/MiWatchLiteSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miwatch/MiWatchLiteSupport.java deleted file mode 100644 index f4356c5af..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miwatch/MiWatchLiteSupport.java +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright (C) 2023 Andreas Shimokawa - - This file is part of Gadgetbridge. - - Gadgetbridge is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Gadgetbridge is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.service.devices.miwatch; - -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCharacteristic; -import android.content.SharedPreferences; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.UUID; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto; -import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; -import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; -import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiAuthService; -import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport; -import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; -import nodomain.freeyourgadget.gadgetbridge.util.GB; - -public class MiWatchLiteSupport extends XiaomiSupport { - - private static final Logger LOG = LoggerFactory.getLogger(MiWatchLiteSupport.class); - - private static final UUID UUID_CHARACTERISTIC_MAIN = UUID.fromString("16186f02-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_UNK1 = UUID.fromString("16186f01-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_UNK2 = UUID.fromString("16186f03-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_UNK3 = UUID.fromString("16186f04-0000-1000-8000-00807f9b34fb"); - private static final UUID UUID_CHARACTERISTIC_UNK4 = UUID.fromString("16186f05-0000-1000-8000-00807f9b34fb"); - - public MiWatchLiteSupport() { - super(); // FIXME: no we do not want to do this!! This adds supported characteristics which we do not have - but we have to. - addSupportedService(UUID.fromString("16186f00-0000-1000-8000-00807f9b34fb")); - } - - @Override - protected TransactionBuilder initializeDevice(final TransactionBuilder builder) { - - // FIXME why is this needed? - getDevice().setFirmwareVersion("..."); - //getDevice().setFirmwareVersion2("..."); - enableNotifications(builder, true); - builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); - builder.requestMtu(247); - String userId = getUserId(gbDevice); - - final XiaomiProto.Auth auth = XiaomiProto.Auth.newBuilder() - .setUserId(userId) - .build(); - - final XiaomiProto.Command command = XiaomiProto.Command.newBuilder() - .setType(XiaomiAuthService.COMMAND_TYPE) - .setSubtype(XiaomiAuthService.CMD_SEND_USERID) - .setAuth(auth) - .build(); - sendCommand(builder, command); - - builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); - - - return builder; - } - - private void enableNotifications(TransactionBuilder builder, boolean enable) { - builder.notify(getCharacteristic(UUID_CHARACTERISTIC_MAIN), enable); - builder.notify(getCharacteristic(UUID_CHARACTERISTIC_UNK1), enable); - builder.notify(getCharacteristic(UUID_CHARACTERISTIC_UNK2), enable); - builder.notify(getCharacteristic(UUID_CHARACTERISTIC_UNK3), enable); - builder.notify(getCharacteristic(UUID_CHARACTERISTIC_UNK4), enable); - } - - protected static String getUserId(final GBDevice device) { - final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()); - - final String authKey = sharedPrefs.getString("authkey", null); - if (StringUtils.isNotBlank(authKey)) { - return authKey; - } - - return "0000000000"; - } - - @Override - public void sendCommand(final TransactionBuilder builder, final nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto.Command command) { - final byte[] commandBytes = command.toByteArray(); - final int commandLength = 2 + commandBytes.length; - if (commandLength > getMTU()) { - LOG.warn("Command with {} bytes is too large for MTU of {}", commandLength, getMTU()); - } - builder.write(getCharacteristic(UUID_CHARACTERISTIC_MAIN), new byte[]{0x00, 0x00, 0x00, 0x00, 0x01, 0x00}); - builder.wait(500); - - final ByteBuffer buf = ByteBuffer.allocate(commandLength).order(ByteOrder.LITTLE_ENDIAN); - buf.put((byte) 1); - buf.put((byte) 0); - buf.put(commandBytes); - LOG.debug("Sending command {} as {}", GB.hexdump(commandBytes), GB.hexdump(buf.array())); - builder.write(getCharacteristic(UUID_CHARACTERISTIC_MAIN), buf.array()); - } - - @Override - public boolean onCharacteristicChanged(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic) { - if (super.onCharacteristicChanged(gatt, characteristic)) { - return true; - } - - UUID characteristicUUID = characteristic.getUuid(); - - final byte[] success_bytes = new byte[]{0x00, 0x00, 0x01, 0x01, 0x00, 0x00}; - final byte[] ping_request = new byte[]{0x00, 0x00, 0x00, 0x00, 0x01, 0x00}; - if (characteristicUUID.equals(UUID_CHARACTERISTIC_UNK1)) { - if (Arrays.equals(ping_request, characteristic.getValue())) { - TransactionBuilder builder = new TransactionBuilder("reply ping"); - builder.write(getCharacteristic(UUID_CHARACTERISTIC_UNK1), new byte[]{0x00, 0x00, 0x01, 0x01}); - builder.queue(getQueue()); - return true; - } - if (ArrayUtils.startsWith(characteristic.getValue(), new byte[]{1, 0, 8})) { - TransactionBuilder builder = new TransactionBuilder("ack whatever"); - builder.write(getCharacteristic(UUID_CHARACTERISTIC_UNK1), new byte[]{0x00, 0x00, 0x01, 0x00}); - builder.queue(getQueue()); - return true; - } - - } - LOG.info("Unhandled characteristic changed: " + characteristicUUID); - return false; - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java index d2e4d3b95..6c7f664db 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java @@ -38,8 +38,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; public class XiaomiCharacteristic { public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0}; - public static final byte[] PAYLOAD_CHUNKED_START_ACK = new byte[]{0, 0, 1, 1}; - public static final byte[] PAYLOAD_CHUNKED_END_ACK = new byte[]{0, 0, 1, 0}; private final Logger LOG; @@ -301,13 +299,13 @@ public class XiaomiCharacteristic { private void sendChunkStartAck() { final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunked start ack"); - builder.write(bluetoothGattCharacteristic, PAYLOAD_CHUNKED_START_ACK); + builder.write(bluetoothGattCharacteristic, new byte[]{0x00, 0x00, 0x01, 0x01}); builder.queue(mSupport.getQueue()); } private void sendChunkEndAck() { final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunked end ack"); - builder.write(bluetoothGattCharacteristic, PAYLOAD_CHUNKED_END_ACK); + builder.write(bluetoothGattCharacteristic, new byte[]{0x00, 0x00, 0x01, 0x00}); builder.queue(mSupport.getQueue()); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java index 87c17cd91..8e51887e1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiEncryptedSupport.java @@ -68,12 +68,13 @@ public class XiaomiEncryptedSupport extends XiaomiSupport { final BluetoothGattCharacteristic btCharacteristicActivityData = getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_ACTIVITY_DATA); final BluetoothGattCharacteristic btCharacteristicDataUpload = getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_DATA_UPLOAD); - if (btCharacteristicCommandRead == null || btCharacteristicCommandWrite == null || btCharacteristicActivityData == null || btCharacteristicDataUpload == null) { + if (btCharacteristicCommandRead == null || btCharacteristicCommandWrite == null) { LOG.warn("Characteristics are null, will attempt to reconnect"); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext())); return builder; } + // TODO move this initialization to upstream class this.characteristicCommandRead = new XiaomiCharacteristic(this, btCharacteristicCommandRead, authService); this.characteristicCommandRead.setEncrypted(true); this.characteristicCommandRead.setHandler(this::handleCommandBytes); @@ -96,6 +97,7 @@ public class XiaomiEncryptedSupport extends XiaomiSupport { builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), true); builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_ACTIVITY_DATA), true); builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_DATA_UPLOAD), true); + authService.startEncryptedHandshake(builder); return builder; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java new file mode 100644 index 000000000..ff668849a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPlaintextSupport.java @@ -0,0 +1,122 @@ +/* Copyright (C) 2023 Andreas Shimokawa + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi; + +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.SharedPreferences; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class XiaomiPlaintextSupport extends XiaomiSupport { + + private static final Logger LOG = LoggerFactory.getLogger(XiaomiPlaintextSupport.class); + + private static final UUID UUID_CHARACTERISTIC_MAIN_READ = UUID.fromString("16186f01-0000-1000-8000-00807f9b34fb"); + private static final UUID UUID_CHARACTERISTIC_MAIN_WRITE = UUID.fromString("16186f02-0000-1000-8000-00807f9b34fb"); + private static final UUID UUID_CHARACTERISTIC_ACTIVITY_DATA = UUID.fromString("16186f03-0000-1000-8000-00807f9b34fb"); + private static final UUID UUID_CHARACTERISTIC_DATA_UPLOAD = UUID.fromString("16186f04-0000-1000-8000-00807f9b34fb"); + private static final UUID UUID_CHARACTERISTIC_UNK5 = UUID.fromString("16186f05-0000-1000-8000-00807f9b34fb"); + + public XiaomiPlaintextSupport() { + super(); + addSupportedService(UUID.fromString("16186f00-0000-1000-8000-00807f9b34fb")); + } + + @Override + protected TransactionBuilder initializeDevice(final TransactionBuilder builder) { + final BluetoothGattCharacteristic btCharacteristicCommandRead = getCharacteristic(UUID_CHARACTERISTIC_MAIN_READ); + final BluetoothGattCharacteristic btCharacteristicCommandWrite = getCharacteristic(UUID_CHARACTERISTIC_MAIN_WRITE); + final BluetoothGattCharacteristic btCharacteristicActivityData = getCharacteristic(UUID_CHARACTERISTIC_ACTIVITY_DATA); + final BluetoothGattCharacteristic btCharacteristicDataUpload = getCharacteristic(UUID_CHARACTERISTIC_DATA_UPLOAD); + + if (btCharacteristicCommandRead == null || btCharacteristicCommandWrite == null) { + LOG.warn("Characteristics are null, will attempt to reconnect"); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext())); + return builder; + } + + // TODO move this initialization to upstream class + this.characteristicCommandRead = new XiaomiCharacteristic(this, btCharacteristicCommandRead, authService); + this.characteristicCommandRead.setEncrypted(false); + this.characteristicCommandRead.setHandler(this::handleCommandBytes); + this.characteristicCommandWrite = new XiaomiCharacteristic(this, btCharacteristicCommandWrite, authService); + this.characteristicCommandRead.setEncrypted(false); + this.characteristicActivityData = new XiaomiCharacteristic(this, btCharacteristicActivityData, authService); + this.characteristicActivityData.setHandler(healthService.getActivityFetcher()::addChunk); + this.characteristicCommandRead.setEncrypted(false); + this.characteristicDataUpload = new XiaomiCharacteristic(this, btCharacteristicDataUpload, authService); + this.characteristicCommandRead.setEncrypted(false); + + // FIXME why is this needed? + getDevice().setFirmwareVersion("..."); + //getDevice().setFirmwareVersion2("..."); + + enableNotifications(builder, true); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + builder.requestMtu(247); + String userId = getUserId(gbDevice); + + final XiaomiProto.Auth auth = XiaomiProto.Auth.newBuilder() + .setUserId(userId) + .build(); + + final XiaomiProto.Command command = XiaomiProto.Command.newBuilder() + .setType(XiaomiAuthService.COMMAND_TYPE) + .setSubtype(XiaomiAuthService.CMD_SEND_USERID) + .setAuth(auth) + .build(); + + sendCommand(builder, command); + + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + + + return builder; + } + + private void enableNotifications(TransactionBuilder builder, boolean enable) { + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_MAIN_WRITE), enable); + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_MAIN_READ), enable); + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_ACTIVITY_DATA), enable); + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_DATA_UPLOAD), enable); + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_UNK5), enable); + } + + protected static String getUserId(final GBDevice device) { + final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()); + + final String authKey = sharedPrefs.getString("authkey", null); + if (StringUtils.isNotBlank(authKey)) { + return authKey; + } + + return "0000000000"; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java index e3f8ae022..1441ea584 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java @@ -394,12 +394,14 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport { } public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) { + // FIXME builder is ignored final byte[] commandBytes = command.toByteArray(); LOG.debug("Sending command {}", GB.hexdump(commandBytes)); this.characteristicCommandWrite.write(commandBytes); } public void sendCommand(final TransactionBuilder builder, final byte[] commandBytes) { + // FIXME builder is ignored this.characteristicCommandWrite.write(commandBytes); }