mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-11 01:21:56 +01:00
Xiaomi: Introduce XiaomiConnectionSupport
Co-Authored-By: José Rebelo <joserebelo@outlook.com>
This commit is contained in:
parent
25dcba23c3
commit
98e8ec2329
@ -57,12 +57,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|||||||
public class XiaomiAuthService extends AbstractXiaomiService {
|
public class XiaomiAuthService extends AbstractXiaomiService {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiAuthService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(XiaomiAuthService.class);
|
||||||
|
|
||||||
public static final byte[] PAYLOAD_HEADER_AUTH = new byte[]{0, 0, 2, 2};
|
|
||||||
|
|
||||||
public static final int COMMAND_TYPE = 1;
|
public static final int COMMAND_TYPE = 1;
|
||||||
|
|
||||||
public static final int CMD_SEND_USERID = 5;
|
public static final int CMD_SEND_USERID = 5;
|
||||||
|
|
||||||
public static final int CMD_NONCE = 26;
|
public static final int CMD_NONCE = 26;
|
||||||
public static final int CMD_AUTH = 27;
|
public static final int CMD_AUTH = 27;
|
||||||
|
|
||||||
@ -83,7 +81,8 @@ public class XiaomiAuthService extends AbstractXiaomiService {
|
|||||||
return encryptionInitialized;
|
return encryptionInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startEncryptedHandshake(final TransactionBuilder builder) {
|
// TODO also implement for spp
|
||||||
|
protected void startEncryptedHandshake(final XiaomiBleSupport support, final TransactionBuilder builder) {
|
||||||
encryptionInitialized = false;
|
encryptionInitialized = false;
|
||||||
|
|
||||||
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.AUTHENTICATING, getSupport().getContext()));
|
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.AUTHENTICATING, getSupport().getContext()));
|
||||||
@ -91,10 +90,10 @@ public class XiaomiAuthService extends AbstractXiaomiService {
|
|||||||
System.arraycopy(getSecretKey(getSupport().getDevice()), 0, secretKey, 0, 16);
|
System.arraycopy(getSecretKey(getSupport().getDevice()), 0, secretKey, 0, 16);
|
||||||
new SecureRandom().nextBytes(nonce);
|
new SecureRandom().nextBytes(nonce);
|
||||||
|
|
||||||
getSupport().sendCommand(builder, buildNonceCommand(nonce));
|
support.sendCommand(builder, buildNonceCommand(nonce));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startClearTextHandshake(final TransactionBuilder builder) {
|
protected void startClearTextHandshake(final XiaomiBleSupport support, final TransactionBuilder builder) {
|
||||||
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.AUTHENTICATING, getSupport().getContext()));
|
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.AUTHENTICATING, getSupport().getContext()));
|
||||||
|
|
||||||
final XiaomiProto.Auth auth = XiaomiProto.Auth.newBuilder()
|
final XiaomiProto.Auth auth = XiaomiProto.Auth.newBuilder()
|
||||||
@ -107,7 +106,7 @@ public class XiaomiAuthService extends AbstractXiaomiService {
|
|||||||
.setAuth(auth)
|
.setAuth(auth)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
getSupport().sendCommand(builder, command);
|
support.sendCommand(builder, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -121,33 +120,27 @@ public class XiaomiAuthService extends AbstractXiaomiService {
|
|||||||
LOG.debug("Got watch nonce");
|
LOG.debug("Got watch nonce");
|
||||||
|
|
||||||
// Watch nonce
|
// Watch nonce
|
||||||
final XiaomiProto.Command reply = handleWatchNonce(cmd.getAuth().getWatchNonce());
|
final XiaomiProto.Command command = handleWatchNonce(cmd.getAuth().getWatchNonce());
|
||||||
if (reply == null) {
|
if (command == null) {
|
||||||
getSupport().disconnect();
|
getSupport().disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TransactionBuilder builder = getSupport().createTransactionBuilder("auth step 2");
|
getSupport().sendCommand("auth step 2", command);
|
||||||
// TODO use sendCommand
|
|
||||||
builder.write(
|
|
||||||
getSupport().getCharacteristic(getSupport().characteristicCommandWrite.getCharacteristicUUID()),
|
|
||||||
ArrayUtils.addAll(PAYLOAD_HEADER_AUTH, reply.toByteArray())
|
|
||||||
);
|
|
||||||
builder.queue(getSupport().getQueue());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CMD_AUTH:
|
case CMD_AUTH:
|
||||||
case CMD_SEND_USERID: {
|
case CMD_SEND_USERID: {
|
||||||
if (cmd.getSubtype() == CMD_AUTH || cmd.getAuth().getStatus() == 1) {
|
if (cmd.getSubtype() == CMD_AUTH || cmd.getAuth().getStatus() == 1) {
|
||||||
LOG.info("Authenticated!");
|
|
||||||
|
|
||||||
encryptionInitialized = cmd.getSubtype() == CMD_AUTH;
|
encryptionInitialized = cmd.getSubtype() == CMD_AUTH;
|
||||||
|
|
||||||
final TransactionBuilder builder = getSupport().createTransactionBuilder("phase 2 initialize");
|
LOG.info("Authenticated, further communications are {}", encryptionInitialized ? "encrypted" : "in plaintext");
|
||||||
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.INITIALIZED, getSupport().getContext()));
|
|
||||||
getSupport().phase2Initialize();
|
getSupport().getDevice().setState(GBDevice.State.INITIALIZED);
|
||||||
builder.queue(getSupport().getQueue());
|
getSupport().getDevice().sendDeviceUpdateIntent(getSupport().getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
|
||||||
|
|
||||||
|
getSupport().onAuthSuccess();
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("could not authenticate");
|
LOG.warn("could not authenticate");
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,242 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class XiaomiBleSupport extends XiaomiConnectionSupport {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(XiaomiBleSupport.class);
|
||||||
|
|
||||||
|
private XiaomiCharacteristic characteristicCommandRead;
|
||||||
|
private XiaomiCharacteristic characteristicCommandWrite;
|
||||||
|
private XiaomiCharacteristic characteristicActivityData;
|
||||||
|
private XiaomiCharacteristic characteristicDataUpload;
|
||||||
|
|
||||||
|
private final XiaomiSupport mXiaomiSupport;
|
||||||
|
|
||||||
|
final AbstractBTLEDeviceSupport commsSupport = new AbstractBTLEDeviceSupport(LOG) {
|
||||||
|
@Override
|
||||||
|
public boolean useAutoConnect() {
|
||||||
|
return mXiaomiSupport.useAutoConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<UUID> getSupportedServices() {
|
||||||
|
return XiaomiBleUuids.UUIDS.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final TransactionBuilder initializeDevice(final TransactionBuilder builder) {
|
||||||
|
XiaomiBleUuids.XiaomiBleUuidSet uuidSet = null;
|
||||||
|
BluetoothGattCharacteristic btCharacteristicCommandRead = null;
|
||||||
|
BluetoothGattCharacteristic btCharacteristicCommandWrite = null;
|
||||||
|
BluetoothGattCharacteristic btCharacteristicActivityData = null;
|
||||||
|
BluetoothGattCharacteristic btCharacteristicDataUpload = null;
|
||||||
|
|
||||||
|
// Attempt to find a known xiaomi service
|
||||||
|
for (Map.Entry<UUID, XiaomiBleUuids.XiaomiBleUuidSet> xiaomiUuid : XiaomiBleUuids.UUIDS.entrySet()) {
|
||||||
|
if (getSupportedServices().contains(xiaomiUuid.getKey())) {
|
||||||
|
LOG.debug("Found Xiaomi service: {}", xiaomiUuid.getKey());
|
||||||
|
uuidSet = xiaomiUuid.getValue();
|
||||||
|
|
||||||
|
btCharacteristicCommandRead = getCharacteristic(uuidSet.getCharacteristicCommandRead());
|
||||||
|
btCharacteristicCommandWrite = getCharacteristic(uuidSet.getCharacteristicCommandWrite());
|
||||||
|
btCharacteristicActivityData = getCharacteristic(uuidSet.getCharacteristicActivityData());
|
||||||
|
btCharacteristicDataUpload = getCharacteristic(uuidSet.getCharacteristicDataUpload());
|
||||||
|
if (btCharacteristicCommandRead == null) {
|
||||||
|
LOG.warn("btCharacteristicCommandRead characteristicc is null");
|
||||||
|
continue;
|
||||||
|
} else if (btCharacteristicCommandWrite == null) {
|
||||||
|
LOG.warn("btCharacteristicCommandWrite characteristicc is null");
|
||||||
|
continue;
|
||||||
|
} else if (btCharacteristicActivityData == null) {
|
||||||
|
LOG.warn("btCharacteristicActivityData characteristicc is null");
|
||||||
|
continue;
|
||||||
|
} else if (btCharacteristicDataUpload == null) {
|
||||||
|
LOG.warn("btCharacteristicDataUpload characteristicc is null");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uuidSet == null) {
|
||||||
|
GB.toast(getContext(), "Failed to find known Xiaomi service", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
LOG.warn("Failed to find known Xiaomi service");
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME unsetDynamicState unsets the fw version, which causes problems..
|
||||||
|
if (getDevice().getFirmwareVersion() == null && mXiaomiSupport.getCachedFirmwareVersion() != null) {
|
||||||
|
getDevice().setFirmwareVersion(mXiaomiSupport.getCachedFirmwareVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
XiaomiBleSupport.this.characteristicCommandRead = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicCommandRead, mXiaomiSupport.getAuthService());
|
||||||
|
XiaomiBleSupport.this.characteristicCommandRead.setEncrypted(uuidSet.isEncrypted());
|
||||||
|
XiaomiBleSupport.this.characteristicCommandRead.setChannelHandler(mXiaomiSupport::handleCommandBytes);
|
||||||
|
XiaomiBleSupport.this.characteristicCommandWrite = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicCommandWrite, mXiaomiSupport.getAuthService());
|
||||||
|
XiaomiBleSupport.this.characteristicCommandWrite.setEncrypted(uuidSet.isEncrypted());
|
||||||
|
XiaomiBleSupport.this.characteristicActivityData = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicActivityData, mXiaomiSupport.getAuthService());
|
||||||
|
XiaomiBleSupport.this.characteristicActivityData.setChannelHandler(mXiaomiSupport.getHealthService().getActivityFetcher()::addChunk);
|
||||||
|
XiaomiBleSupport.this.characteristicActivityData.setEncrypted(uuidSet.isEncrypted());
|
||||||
|
XiaomiBleSupport.this.characteristicDataUpload = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicDataUpload, mXiaomiSupport.getAuthService());
|
||||||
|
XiaomiBleSupport.this.characteristicDataUpload.setEncrypted(uuidSet.isEncrypted());
|
||||||
|
XiaomiBleSupport.this.characteristicDataUpload.setIncrementNonce(false);
|
||||||
|
|
||||||
|
mXiaomiSupport.getDataUploadService().setDataUploadCharacteristic(XiaomiBleSupport.this.characteristicDataUpload);
|
||||||
|
|
||||||
|
builder.requestMtu(247);
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
|
builder.notify(btCharacteristicCommandWrite, true);
|
||||||
|
builder.notify(btCharacteristicCommandRead, true);
|
||||||
|
builder.notify(btCharacteristicActivityData, true);
|
||||||
|
builder.notify(btCharacteristicDataUpload, true);
|
||||||
|
|
||||||
|
if (uuidSet.isEncrypted()) {
|
||||||
|
mXiaomiSupport.getAuthService().startEncryptedHandshake(XiaomiBleSupport.this, builder);
|
||||||
|
} else {
|
||||||
|
mXiaomiSupport.getAuthService().startClearTextHandshake(XiaomiBleSupport.this, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
|
||||||
|
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final UUID characteristicUUID = characteristic.getUuid();
|
||||||
|
final byte[] value = characteristic.getValue();
|
||||||
|
|
||||||
|
if (characteristicCommandRead.getCharacteristicUUID().equals(characteristicUUID)) {
|
||||||
|
characteristicCommandRead.onCharacteristicChanged(value);
|
||||||
|
return true;
|
||||||
|
} else if (characteristicCommandWrite.getCharacteristicUUID().equals(characteristicUUID)) {
|
||||||
|
characteristicCommandWrite.onCharacteristicChanged(value);
|
||||||
|
return true;
|
||||||
|
} else if (characteristicActivityData.getCharacteristicUUID().equals(characteristicUUID)) {
|
||||||
|
characteristicActivityData.onCharacteristicChanged(value);
|
||||||
|
return true;
|
||||||
|
} else if (characteristicDataUpload.getCharacteristicUUID().equals(characteristicUUID)) {
|
||||||
|
characteristicDataUpload.onCharacteristicChanged(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Unhandled characteristic changed: {} {}", characteristicUUID, GB.hexdump(value));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getImplicitCallbackModify() {
|
||||||
|
return mXiaomiSupport.getImplicitCallbackModify();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public XiaomiBleSupport(final XiaomiSupport xiaomiSupport) {
|
||||||
|
this.mXiaomiSupport = xiaomiSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAuthSuccess() {
|
||||||
|
characteristicCommandRead.reset();
|
||||||
|
characteristicCommandWrite.reset();
|
||||||
|
characteristicActivityData.reset();
|
||||||
|
characteristicDataUpload.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(GBDevice device, BluetoothAdapter adapter, Context context) {
|
||||||
|
this.commsSupport.setContext(device, adapter, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
this.commsSupport.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCommand(final String taskName, final XiaomiProto.Command command) {
|
||||||
|
if (this.characteristicCommandWrite == null) {
|
||||||
|
// Can sometimes happen in race conditions when connecting + receiving calendar event or weather updates
|
||||||
|
LOG.warn("characteristicCommandWrite is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.characteristicCommandWrite.write(taskName, command.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realistically, this function should only be used during auth, as we must schedule the command after
|
||||||
|
* notifications were enabled on the characteristics, and for that we need the builder to guarantee the
|
||||||
|
* order.
|
||||||
|
*/
|
||||||
|
public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) {
|
||||||
|
if (this.characteristicCommandWrite == null) {
|
||||||
|
// Can sometimes happen in race conditions when connecting + receiving calendar event or weather updates
|
||||||
|
LOG.warn("characteristicCommandWrite is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.characteristicCommandWrite.write(builder, command.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransactionBuilder createTransactionBuilder(String taskName) {
|
||||||
|
return commsSupport.createTransactionBuilder(taskName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BtLEQueue getQueue() {
|
||||||
|
return commsSupport.getQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUploadProgress(int textRsrc, int progressPercent) {
|
||||||
|
try {
|
||||||
|
final TransactionBuilder builder = commsSupport.createTransactionBuilder("send data upload progress");
|
||||||
|
builder.add(new SetProgressAction(
|
||||||
|
commsSupport.getContext().getString(textRsrc),
|
||||||
|
true,
|
||||||
|
progressPercent,
|
||||||
|
commsSupport.getContext()
|
||||||
|
));
|
||||||
|
builder.queue(commsSupport.getQueue());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to update progress notification", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean connect() {
|
||||||
|
return commsSupport.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
commsSupport.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
|
||||||
|
|
||||||
|
public interface XiaomiChannelHandler {
|
||||||
|
void handle(final byte[] payload);
|
||||||
|
}
|
@ -45,7 +45,7 @@ public class XiaomiCharacteristic {
|
|||||||
// max chunk size, including headers
|
// max chunk size, including headers
|
||||||
public static final int MAX_WRITE_SIZE = 242;
|
public static final int MAX_WRITE_SIZE = 242;
|
||||||
|
|
||||||
private final XiaomiSupport mSupport;
|
private final XiaomiBleSupport mSupport;
|
||||||
|
|
||||||
private final BluetoothGattCharacteristic bluetoothGattCharacteristic;
|
private final BluetoothGattCharacteristic bluetoothGattCharacteristic;
|
||||||
private final UUID characteristicUUID;
|
private final UUID characteristicUUID;
|
||||||
@ -68,11 +68,11 @@ public class XiaomiCharacteristic {
|
|||||||
private boolean sendingChunked = false;
|
private boolean sendingChunked = false;
|
||||||
private Payload currentPayload = null;
|
private Payload currentPayload = null;
|
||||||
|
|
||||||
private Handler handler = null;
|
private XiaomiChannelHandler channelHandler = null;
|
||||||
|
|
||||||
private SendCallback callback;
|
private SendCallback callback;
|
||||||
|
|
||||||
public XiaomiCharacteristic(final XiaomiSupport support,
|
public XiaomiCharacteristic(final XiaomiBleSupport support,
|
||||||
final BluetoothGattCharacteristic bluetoothGattCharacteristic,
|
final BluetoothGattCharacteristic bluetoothGattCharacteristic,
|
||||||
@Nullable final XiaomiAuthService authService) {
|
@Nullable final XiaomiAuthService authService) {
|
||||||
this.mSupport = support;
|
this.mSupport = support;
|
||||||
@ -86,8 +86,8 @@ public class XiaomiCharacteristic {
|
|||||||
return characteristicUUID;
|
return characteristicUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHandler(final Handler handler) {
|
public void setChannelHandler(final XiaomiChannelHandler handler) {
|
||||||
this.handler = handler;
|
this.channelHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCallback(final SendCallback callback) {
|
public void setCallback(final SendCallback callback) {
|
||||||
@ -162,11 +162,15 @@ public class XiaomiCharacteristic {
|
|||||||
if (chunk == numChunks) {
|
if (chunk == numChunks) {
|
||||||
sendChunkEndAck();
|
sendChunkEndAck();
|
||||||
|
|
||||||
|
if (channelHandler != null) {
|
||||||
if (isEncrypted) {
|
if (isEncrypted) {
|
||||||
// chunks are always encrypted if an auth service is available
|
// chunks are always encrypted if an auth service is available
|
||||||
handler.handle(authService.decrypt(chunkBuffer.toByteArray()));
|
channelHandler.handle(authService.decrypt(chunkBuffer.toByteArray()));
|
||||||
} else {
|
} else {
|
||||||
handler.handle(chunkBuffer.toByteArray());
|
channelHandler.handle(chunkBuffer.toByteArray());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warn("Channel handler for char {} is null!", characteristicUUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentChunk = 0;
|
currentChunk = 0;
|
||||||
@ -249,7 +253,10 @@ public class XiaomiCharacteristic {
|
|||||||
buf.get(plainValue);
|
buf.get(plainValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.handle(plainValue);
|
if (channelHandler != null)
|
||||||
|
channelHandler.handle(plainValue);
|
||||||
|
else
|
||||||
|
LOG.warn("Channel handler for char {} is null!", characteristicUUID);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case 3:
|
case 3:
|
||||||
@ -362,10 +369,6 @@ public class XiaomiCharacteristic {
|
|||||||
builder.queue(mSupport.getQueue());
|
builder.queue(mSupport.getQueue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Handler {
|
|
||||||
void handle(final byte[] payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Payload {
|
private static class Payload {
|
||||||
private final String taskName;
|
private final String taskName;
|
||||||
private final byte[] bytes;
|
private final byte[] bytes;
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/* Copyright (C) 2023 José Rebelo, Yoran Vulker
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
|
|
||||||
|
public abstract class XiaomiConnectionSupport {
|
||||||
|
public abstract boolean connect();
|
||||||
|
public abstract void onAuthSuccess();
|
||||||
|
public abstract void onUploadProgress(int textRsrc, int progressPercent);
|
||||||
|
public abstract void dispose();
|
||||||
|
public abstract void setContext(final GBDevice device, final BluetoothAdapter adapter, final Context context);
|
||||||
|
public abstract void disconnect();
|
||||||
|
public abstract void sendCommand(final String taskName, final XiaomiProto.Command command);
|
||||||
|
}
|
@ -18,12 +18,9 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
|
|||||||
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothGatt;
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -42,6 +39,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
|
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
@ -57,9 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
|
||||||
@ -77,27 +73,23 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
public class XiaomiSupport extends AbstractDeviceSupport {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class);
|
private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class);
|
||||||
|
|
||||||
protected XiaomiCharacteristic characteristicCommandRead;
|
private final XiaomiAuthService authService = new XiaomiAuthService(this);
|
||||||
protected XiaomiCharacteristic characteristicCommandWrite;
|
private final XiaomiMusicService musicService = new XiaomiMusicService(this);
|
||||||
protected XiaomiCharacteristic characteristicActivityData;
|
private final XiaomiHealthService healthService = new XiaomiHealthService(this);
|
||||||
protected XiaomiCharacteristic characteristicDataUpload;
|
private final XiaomiNotificationService notificationService = new XiaomiNotificationService(this);
|
||||||
|
private final XiaomiScheduleService scheduleService = new XiaomiScheduleService(this);
|
||||||
|
private final XiaomiWeatherService weatherService = new XiaomiWeatherService(this);
|
||||||
|
private final XiaomiSystemService systemService = new XiaomiSystemService(this);
|
||||||
|
private final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
|
||||||
|
private final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
|
||||||
|
private final XiaomiDataUploadService dataUploadService = new XiaomiDataUploadService(this);
|
||||||
|
private final XiaomiPhonebookService phonebookService = new XiaomiPhonebookService(this);
|
||||||
|
|
||||||
protected final XiaomiAuthService authService = new XiaomiAuthService(this);
|
private String cachedFirmwareVersion = null;
|
||||||
protected final XiaomiMusicService musicService = new XiaomiMusicService(this);
|
private XiaomiConnectionSupport connectionSupport = null;
|
||||||
protected final XiaomiHealthService healthService = new XiaomiHealthService(this);
|
|
||||||
protected final XiaomiNotificationService notificationService = new XiaomiNotificationService(this);
|
|
||||||
protected final XiaomiScheduleService scheduleService = new XiaomiScheduleService(this);
|
|
||||||
protected final XiaomiWeatherService weatherService = new XiaomiWeatherService(this);
|
|
||||||
protected final XiaomiSystemService systemService = new XiaomiSystemService(this);
|
|
||||||
protected final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
|
|
||||||
protected final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
|
|
||||||
protected final XiaomiDataUploadService dataUploadService = new XiaomiDataUploadService(this);
|
|
||||||
protected final XiaomiPhonebookService phonebookService = new XiaomiPhonebookService(this);
|
|
||||||
|
|
||||||
private String mFirmwareVersion = null;
|
|
||||||
|
|
||||||
private final Map<Integer, AbstractXiaomiService> mServiceMap = new LinkedHashMap<Integer, AbstractXiaomiService>() {{
|
private final Map<Integer, AbstractXiaomiService> mServiceMap = new LinkedHashMap<Integer, AbstractXiaomiService>() {{
|
||||||
put(XiaomiAuthService.COMMAND_TYPE, authService);
|
put(XiaomiAuthService.COMMAND_TYPE, authService);
|
||||||
@ -113,98 +105,6 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
put(XiaomiPhonebookService.COMMAND_TYPE, phonebookService);
|
put(XiaomiPhonebookService.COMMAND_TYPE, phonebookService);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
public XiaomiSupport() {
|
|
||||||
super(LOG);
|
|
||||||
for (final UUID uuid : XiaomiBleUuids.UUIDS.keySet()) {
|
|
||||||
addSupportedService(uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final TransactionBuilder initializeDevice(final TransactionBuilder builder) {
|
|
||||||
XiaomiBleUuids.XiaomiBleUuidSet uuidSet = null;
|
|
||||||
BluetoothGattCharacteristic btCharacteristicCommandRead = null;
|
|
||||||
BluetoothGattCharacteristic btCharacteristicCommandWrite = null;
|
|
||||||
BluetoothGattCharacteristic btCharacteristicActivityData = null;
|
|
||||||
BluetoothGattCharacteristic btCharacteristicDataUpload = null;
|
|
||||||
|
|
||||||
// Attempt to find a known xiaomi service
|
|
||||||
for (Map.Entry<UUID, XiaomiBleUuids.XiaomiBleUuidSet> xiaomiUuid : XiaomiBleUuids.UUIDS.entrySet()) {
|
|
||||||
if (getSupportedServices().contains(xiaomiUuid.getKey())) {
|
|
||||||
LOG.debug("Found Xiaomi service: {}", xiaomiUuid.getKey());
|
|
||||||
uuidSet = xiaomiUuid.getValue();
|
|
||||||
|
|
||||||
btCharacteristicCommandRead = getCharacteristic(uuidSet.getCharacteristicCommandRead());
|
|
||||||
btCharacteristicCommandWrite = getCharacteristic(uuidSet.getCharacteristicCommandWrite());
|
|
||||||
btCharacteristicActivityData = getCharacteristic(uuidSet.getCharacteristicActivityData());
|
|
||||||
btCharacteristicDataUpload = getCharacteristic(uuidSet.getCharacteristicDataUpload());
|
|
||||||
if (btCharacteristicCommandRead == null) {
|
|
||||||
LOG.warn("btCharacteristicCommandRead characteristicc is null");
|
|
||||||
continue;
|
|
||||||
} else if (btCharacteristicCommandWrite == null) {
|
|
||||||
LOG.warn("btCharacteristicCommandWrite characteristicc is null");
|
|
||||||
continue;
|
|
||||||
} else if (btCharacteristicActivityData == null) {
|
|
||||||
LOG.warn("btCharacteristicActivityData characteristicc is null");
|
|
||||||
continue;
|
|
||||||
} else if (btCharacteristicDataUpload == null) {
|
|
||||||
LOG.warn("btCharacteristicDataUpload characteristicc is null");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uuidSet == null) {
|
|
||||||
GB.toast(getContext(), "Failed to find known Xiaomi service", Toast.LENGTH_LONG, GB.ERROR);
|
|
||||||
LOG.warn("Failed to find known Xiaomi service");
|
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME unsetDynamicState unsets the fw version, which causes problems..
|
|
||||||
if (getDevice().getFirmwareVersion() == null && mFirmwareVersion != null) {
|
|
||||||
getDevice().setFirmwareVersion(mFirmwareVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.characteristicCommandRead = new XiaomiCharacteristic(this, btCharacteristicCommandRead, authService);
|
|
||||||
this.characteristicCommandRead.setEncrypted(uuidSet.isEncrypted());
|
|
||||||
this.characteristicCommandRead.setHandler(this::handleCommandBytes);
|
|
||||||
this.characteristicCommandWrite = new XiaomiCharacteristic(this, btCharacteristicCommandWrite, authService);
|
|
||||||
this.characteristicCommandWrite.setEncrypted(uuidSet.isEncrypted());
|
|
||||||
this.characteristicActivityData = new XiaomiCharacteristic(this, btCharacteristicActivityData, authService);
|
|
||||||
this.characteristicActivityData.setHandler(healthService.getActivityFetcher()::addChunk);
|
|
||||||
this.characteristicActivityData.setEncrypted(uuidSet.isEncrypted());
|
|
||||||
this.characteristicDataUpload = new XiaomiCharacteristic(this, btCharacteristicDataUpload, authService);
|
|
||||||
this.characteristicDataUpload.setEncrypted(uuidSet.isEncrypted());
|
|
||||||
this.characteristicDataUpload.setIncrementNonce(false);
|
|
||||||
this.dataUploadService.setDataUploadCharacteristic(this.characteristicDataUpload);
|
|
||||||
|
|
||||||
builder.requestMtu(247);
|
|
||||||
|
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
|
||||||
|
|
||||||
builder.notify(btCharacteristicCommandWrite, true);
|
|
||||||
builder.notify(btCharacteristicCommandRead, true);
|
|
||||||
builder.notify(btCharacteristicActivityData, true);
|
|
||||||
builder.notify(btCharacteristicDataUpload, true);
|
|
||||||
|
|
||||||
if (uuidSet.isEncrypted()) {
|
|
||||||
authService.startEncryptedHandshake(builder);
|
|
||||||
} else {
|
|
||||||
authService.startClearTextHandshake(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean useAutoConnect() {
|
public boolean useAutoConnect() {
|
||||||
return true;
|
return true;
|
||||||
@ -215,44 +115,82 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private XiaomiConnectionSupport createConnectionSpecificSupport() {
|
||||||
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
|
DeviceCoordinator.ConnectionType connType = getCoordinator().getConnectionType();
|
||||||
// FIXME unsetDynamicState unsets the fw version, which causes problems..
|
|
||||||
if (mFirmwareVersion == null && gbDevice.getFirmwareVersion() != null) {
|
switch (connType) {
|
||||||
mFirmwareVersion = gbDevice.getFirmwareVersion();
|
case BLE:
|
||||||
|
case BOTH:
|
||||||
|
return new XiaomiBleSupport(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setContext(gbDevice, btAdapter, context);
|
LOG.error("Cannot create connection-specific support, unhanded {} connection type", connType);
|
||||||
for (final AbstractXiaomiService service : mServiceMap.values()) {
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XiaomiConnectionSupport getConnectionSpecificSupport() {
|
||||||
|
if (connectionSupport == null) {
|
||||||
|
connectionSupport = createConnectionSpecificSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean connect() {
|
||||||
|
if (getConnectionSpecificSupport() != null)
|
||||||
|
return getConnectionSpecificSupport().connect();
|
||||||
|
|
||||||
|
LOG.error("getConnectionSpecificSupport returned null, could not connect");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUploadProgress(int textRsrc, int progressPercent) {
|
||||||
|
if (getConnectionSpecificSupport() == null) {
|
||||||
|
LOG.error("onUploadProgress called but connection specific unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnectionSpecificSupport().onUploadProgress(textRsrc, progressPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (getConnectionSpecificSupport() != null) {
|
||||||
|
getConnectionSpecificSupport().dispose();
|
||||||
|
connectionSupport = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContext(final GBDevice device, final BluetoothAdapter adapter, final Context context) {
|
||||||
|
// FIXME unsetDynamicState unsets the fw version, which causes problems..
|
||||||
|
if (getCachedFirmwareVersion() == null && device.getFirmwareVersion() != null) {
|
||||||
|
setCachedFirmwareVersion(device.getFirmwareVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
super.setContext(device, adapter, context);
|
||||||
|
|
||||||
|
for (AbstractXiaomiService service : mServiceMap.values()) {
|
||||||
service.setContext(context);
|
service.setContext(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getConnectionSpecificSupport() != null) {
|
||||||
|
getConnectionSpecificSupport().setContext(device, adapter, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String getCachedFirmwareVersion() {
|
||||||
public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
|
return this.cachedFirmwareVersion;
|
||||||
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final UUID characteristicUUID = characteristic.getUuid();
|
public void setCachedFirmwareVersion(String version) {
|
||||||
final byte[] value = characteristic.getValue();
|
this.cachedFirmwareVersion = version;
|
||||||
|
|
||||||
if (characteristicCommandRead.getCharacteristicUUID().equals(characteristicUUID)) {
|
|
||||||
characteristicCommandRead.onCharacteristicChanged(value);
|
|
||||||
return true;
|
|
||||||
} else if (characteristicCommandWrite.getCharacteristicUUID().equals(characteristicUUID)) {
|
|
||||||
characteristicCommandWrite.onCharacteristicChanged(value);
|
|
||||||
return true;
|
|
||||||
} else if (characteristicActivityData.getCharacteristicUUID().equals(characteristicUUID)) {
|
|
||||||
characteristicActivityData.onCharacteristicChanged(value);
|
|
||||||
return true;
|
|
||||||
} else if (characteristicDataUpload.getCharacteristicUUID().equals(characteristicUUID)) {
|
|
||||||
characteristicDataUpload.onCharacteristicChanged(value);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.warn("Unhandled characteristic changed: {} {}", characteristicUUID, GB.hexdump(value));
|
public void disconnect() {
|
||||||
return false;
|
if (getConnectionSpecificSupport() != null) {
|
||||||
|
getConnectionSpecificSupport().disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleCommandBytes(final byte[] plainValue) {
|
public void handleCommandBytes(final byte[] plainValue) {
|
||||||
@ -459,13 +397,10 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return (XiaomiCoordinator) gbDevice.getDeviceCoordinator();
|
return (XiaomiCoordinator) gbDevice.getDeviceCoordinator();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void phase2Initialize() {
|
protected void onAuthSuccess() {
|
||||||
LOG.info("phase2Initialize");
|
LOG.info("onAuthSuccess");
|
||||||
|
|
||||||
characteristicCommandRead.reset();
|
getConnectionSpecificSupport().onAuthSuccess();
|
||||||
characteristicCommandWrite.reset();
|
|
||||||
characteristicActivityData.reset();
|
|
||||||
characteristicDataUpload.reset();
|
|
||||||
|
|
||||||
if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
|
if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
|
||||||
systemService.setCurrentTime();
|
systemService.setCurrentTime();
|
||||||
@ -477,28 +412,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sendCommand(final String taskName, final XiaomiProto.Command command) {
|
public void sendCommand(final String taskName, final XiaomiProto.Command command) {
|
||||||
if (this.characteristicCommandWrite == null) {
|
getConnectionSpecificSupport().sendCommand(taskName, command);
|
||||||
// Can sometimes happen in race conditions when connecting + receiving calendar event or weather updates
|
|
||||||
LOG.warn("characteristicCommandWrite is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.characteristicCommandWrite.write(taskName, command.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Realistically, this function should only be used during auth, as we must schedule the command after
|
|
||||||
* notifications were enabled on the characteristics, and for that we need the builder to guarantee the
|
|
||||||
* order.
|
|
||||||
*/
|
|
||||||
public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) {
|
|
||||||
if (this.characteristicCommandWrite == null) {
|
|
||||||
// Can sometimes happen in race conditions when connecting + receiving calendar event or weather updates
|
|
||||||
LOG.warn("characteristicCommandWrite is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.characteristicCommandWrite.write(builder, command.toByteArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendCommand(final String taskName, final int type, final int subtype) {
|
public void sendCommand(final String taskName, final int type, final int subtype) {
|
||||||
@ -511,10 +425,18 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public XiaomiDataUploadService getDataUploader() {
|
public XiaomiAuthService getAuthService() {
|
||||||
|
return this.authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XiaomiDataUploadService getDataUploadService() {
|
||||||
return this.dataUploadService;
|
return this.dataUploadService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XiaomiHealthService getHealthService() {
|
||||||
|
return this.healthService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String customStringFilter(final String inputString) {
|
public String customStringFilter(final String inputString) {
|
||||||
return StringUtils.replaceEach(inputString, EMOJI_SOURCE, EMOJI_TARGET);
|
return StringUtils.replaceEach(inputString, EMOJI_SOURCE, EMOJI_TARGET);
|
||||||
|
@ -538,15 +538,15 @@ public class XiaomiNotificationService extends AbstractXiaomiService implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupport().getDataUploader().setCallback(this);
|
getSupport().getDataUploadService().setCallback(this);
|
||||||
getSupport().getDataUploader().requestUpload(XiaomiDataUploadService.TYPE_NOTIFICATION_ICON, buf.array());
|
getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_NOTIFICATION_ICON, buf.array());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUploadFinish(final boolean success) {
|
public void onUploadFinish(final boolean success) {
|
||||||
LOG.debug("Notification icon upload finished: {}", success);
|
LOG.debug("Notification icon upload finished: {}", success);
|
||||||
|
|
||||||
getSupport().getDataUploader().setCallback(null);
|
getSupport().getDataUploadService().setCallback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -53,8 +53,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
|
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
|
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||||
@ -143,8 +141,8 @@ public class XiaomiSystemService extends AbstractXiaomiService implements Xiaomi
|
|||||||
|
|
||||||
LOG.debug("Firmware install status 0, uploading");
|
LOG.debug("Firmware install status 0, uploading");
|
||||||
setDeviceBusy();
|
setDeviceBusy();
|
||||||
getSupport().getDataUploader().setCallback(this);
|
getSupport().getDataUploadService().setCallback(this);
|
||||||
getSupport().getDataUploader().requestUpload(XiaomiDataUploadService.TYPE_FIRMWARE, fwHelper.getBytes());
|
getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_FIRMWARE, fwHelper.getBytes());
|
||||||
return;
|
return;
|
||||||
case CMD_PASSWORD_GET:
|
case CMD_PASSWORD_GET:
|
||||||
handlePassword(cmd.getSystem().getPassword());
|
handlePassword(cmd.getSystem().getPassword());
|
||||||
@ -315,9 +313,9 @@ public class XiaomiSystemService extends AbstractXiaomiService implements Xiaomi
|
|||||||
gbDeviceEventVersionInfo.fwVersion = deviceInfo.getFirmware();
|
gbDeviceEventVersionInfo.fwVersion = deviceInfo.getFirmware();
|
||||||
//gbDeviceEventVersionInfo.fwVersion2 = "N/A";
|
//gbDeviceEventVersionInfo.fwVersion2 = "N/A";
|
||||||
gbDeviceEventVersionInfo.hwVersion = deviceInfo.getModel();
|
gbDeviceEventVersionInfo.hwVersion = deviceInfo.getModel();
|
||||||
final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("SERIAL: ", deviceInfo.getSerialNumber());
|
|
||||||
|
|
||||||
getSupport().evaluateGBDeviceEvent(gbDeviceEventVersionInfo);
|
getSupport().evaluateGBDeviceEvent(gbDeviceEventVersionInfo);
|
||||||
|
|
||||||
|
final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("SERIAL: ", deviceInfo.getSerialNumber());
|
||||||
getSupport().evaluateGBDeviceEvent(gbDeviceEventUpdateDeviceInfo);
|
getSupport().evaluateGBDeviceEvent(gbDeviceEventUpdateDeviceInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -937,7 +935,7 @@ public class XiaomiSystemService extends AbstractXiaomiService implements Xiaomi
|
|||||||
public void onUploadFinish(final boolean success) {
|
public void onUploadFinish(final boolean success) {
|
||||||
LOG.debug("Firmware upload finished: {}", success);
|
LOG.debug("Firmware upload finished: {}", success);
|
||||||
|
|
||||||
getSupport().getDataUploader().setCallback(null);
|
getSupport().getDataUploadService().setCallback(null);
|
||||||
|
|
||||||
final String notificationMessage = success ?
|
final String notificationMessage = success ?
|
||||||
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_complete) :
|
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_complete) :
|
||||||
@ -952,17 +950,6 @@ public class XiaomiSystemService extends AbstractXiaomiService implements Xiaomi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUploadProgress(final int progressPercent) {
|
public void onUploadProgress(final int progressPercent) {
|
||||||
try {
|
getSupport().onUploadProgress(R.string.updatefirmwareoperation_update_in_progress, progressPercent);
|
||||||
final TransactionBuilder builder = getSupport().createTransactionBuilder("send data upload progress");
|
|
||||||
builder.add(new SetProgressAction(
|
|
||||||
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_in_progress),
|
|
||||||
true,
|
|
||||||
progressPercent,
|
|
||||||
getSupport().getContext()
|
|
||||||
));
|
|
||||||
builder.queue(getSupport().getQueue());
|
|
||||||
} catch (final Exception e) {
|
|
||||||
LOG.error("Failed to update progress notification", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,8 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService implements Xia
|
|||||||
|
|
||||||
LOG.debug("Watchface install status 0, uploading");
|
LOG.debug("Watchface install status 0, uploading");
|
||||||
setDeviceBusy();
|
setDeviceBusy();
|
||||||
getSupport().getDataUploader().setCallback(this);
|
getSupport().getDataUploadService().setCallback(this);
|
||||||
getSupport().getDataUploader().requestUpload(XiaomiDataUploadService.TYPE_WATCHFACE, fwHelper.getBytes());
|
getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_WATCHFACE, fwHelper.getBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,11 +230,11 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService implements Xia
|
|||||||
public void onUploadFinish(final boolean success) {
|
public void onUploadFinish(final boolean success) {
|
||||||
LOG.debug("Watchface upload finished: {}", success);
|
LOG.debug("Watchface upload finished: {}", success);
|
||||||
|
|
||||||
getSupport().getDataUploader().setCallback(null);
|
getSupport().getDataUploadService().setCallback(null);
|
||||||
|
|
||||||
final String notificationMessage = success ?
|
final String notificationMessage = success ?
|
||||||
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_complete) :
|
getSupport().getContext().getString(R.string.uploadwatchfaceoperation_complete) :
|
||||||
getSupport().getContext().getString(R.string.updatefirmwareoperation_write_failed);
|
getSupport().getContext().getString(R.string.uploadwatchfaceoperation_failed);
|
||||||
|
|
||||||
GB.updateInstallNotification(notificationMessage, false, 100, getSupport().getContext());
|
GB.updateInstallNotification(notificationMessage, false, 100, getSupport().getContext());
|
||||||
|
|
||||||
@ -250,23 +250,12 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService implements Xia
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUploadProgress(final int progressPercent) {
|
public void onUploadProgress(final int progressPercent) {
|
||||||
try {
|
getSupport().onUploadProgress(R.string.uploadwatchfaceoperation_in_progress, progressPercent);
|
||||||
final TransactionBuilder builder = getSupport().createTransactionBuilder("send data upload progress");
|
|
||||||
builder.add(new SetProgressAction(
|
|
||||||
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_in_progress),
|
|
||||||
true,
|
|
||||||
progressPercent,
|
|
||||||
getSupport().getContext()
|
|
||||||
));
|
|
||||||
builder.queue(getSupport().getQueue());
|
|
||||||
} catch (final Exception e) {
|
|
||||||
LOG.error("Failed to update progress notification", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDeviceBusy() {
|
private void setDeviceBusy() {
|
||||||
final GBDevice device = getSupport().getDevice();
|
final GBDevice device = getSupport().getDevice();
|
||||||
device.setBusyTask(getSupport().getContext().getString(R.string.updating_firmware));
|
device.setBusyTask(getSupport().getContext().getString(R.string.uploading_watchface));
|
||||||
device.sendDeviceUpdateIntent(getSupport().getContext());
|
device.sendDeviceUpdateIntent(getSupport().getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2572,4 +2572,8 @@
|
|||||||
<string name="pref_title_fossil_hr_nav_vibrate">Vibrate on new instruction</string>
|
<string name="pref_title_fossil_hr_nav_vibrate">Vibrate on new instruction</string>
|
||||||
<string name="pref_summary_fossil_hr_nav_vibrate">Whether the watch should vibrate on every new or changed navigation instruction (only when the app is in the foreground)</string>
|
<string name="pref_summary_fossil_hr_nav_vibrate">Whether the watch should vibrate on every new or changed navigation instruction (only when the app is in the foreground)</string>
|
||||||
<string name="notification_channel_connection_status_name">Connection Status</string>
|
<string name="notification_channel_connection_status_name">Connection Status</string>
|
||||||
|
<string name="uploading_watchface">Uploading watchface…</string>
|
||||||
|
<string name="uploadwatchfaceoperation_in_progress">Uploading watchface</string>
|
||||||
|
<string name="uploadwatchfaceoperation_complete">Watchface installation completed</string>
|
||||||
|
<string name="uploadwatchfaceoperation_failed">Watchface installation failed</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user