mSupportedServices = new HashSet<>(4);
+
+ @Override
+ public boolean connect() {
+ if (mQueue == null) {
+ mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext());
+ }
+ return mQueue.connect();
+ }
+
+ /**
+ * Subclasses should populate the given builder to initialize the device (if necessary).
+ * @param builder
+ * @return the same builder as passed as the argument
+ */
+ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+ return builder;
+ }
+
+ @Override
+ public void dispose() {
+ if (mQueue != null) {
+ mQueue.dispose();
+ }
+ }
+
+ /**
+ * Send commands like this to the device:
+ *
+ * perform("sms notification").write(someCharacteristic, someByteArray).queue(getQueue());
+ *
+ * TODO: support orchestration of multiple reads and writes depending on returned values
+ * @see #performConnected(Transaction)
+ * @see #initializeDevice(TransactionBuilder)
+ */
+ protected TransactionBuilder performInitialized(String taskName) throws IOException {
+ if (!isConnected()) {
+ if (!connect()) {
+ throw new IOException("1: Unable to connect to device: " + getDevice());
+ }
+ }
+ if (!isInitialized()) {
+ // first, add a transaction that performs device initialization
+ TransactionBuilder builder = new TransactionBuilder("Initialize device");
+ initializeDevice(builder).queue(getQueue());
+ }
+ return new TransactionBuilder(taskName);
+ }
+
+ /**
+ *
+ * @param transaction
+ * @throws IOException
+ * @see {@link #performInitialized(String)}
+ */
+ protected void performConnected(Transaction transaction) throws IOException {
+ if (!isConnected()) {
+ if (!connect()) {
+ throw new IOException("2: Unable to connect to device: " + getDevice());
+ }
+ }
+ getQueue().add(transaction);
+ }
+
+ public BtLEQueue getQueue() {
+ return mQueue;
+ }
+
+ /**
+ * Subclasses should call this method to add services they support.
+ * Only supported services will be queried for characteristics.
+ * @param aSupportedService
+ * @see #getCharacteristic(UUID)
+ */
+ protected void addSupportedService(UUID aSupportedService) {
+ mSupportedServices.add(aSupportedService);
+ }
+
+ /**
+ * Returns the characteristic matching the given UUID. Only characteristics
+ * are returned whose service is marked as supported.
+ * @param uuid
+ * @return the characteristic for the given UUID or null
+ * @see #addSupportedService(UUID)
+ */
+ protected BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+ if (mAvailableCharacteristics == null) {
+ return null;
+ }
+ return mAvailableCharacteristics.get(uuid);
+ }
+
+ private void gattServicesDiscovered(List discoveredGattServices) {
+ mAvailableCharacteristics = null;
+
+ if (discoveredGattServices == null) {
+ return;
+ }
+ Set supportedServices = getSupportedServices();
+
+ for (BluetoothGattService service : discoveredGattServices) {
+ if (supportedServices.contains(service.getUuid())) {
+ List characteristics = service.getCharacteristics();
+ if (characteristics == null || characteristics.isEmpty()) {
+ Log.w(TAG, "Supported LE service " + service.getUuid() + "did not return any characteristics");
+ continue;
+ }
+ mAvailableCharacteristics = new HashMap<>(characteristics.size());
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
+ mAvailableCharacteristics.put(characteristic.getUuid(), characteristic);
+ }
+ }
+ }
+ }
+
+ protected Set getSupportedServices() {
+ return mSupportedServices;
+ }
+
+ // default implementations of event handler methods (gatt callbacks)
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt) {
+ gattServicesDiscovered(getQueue().getSupportedGattServices());
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ }
+
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java
new file mode 100644
index 000000000..2e261e314
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+/**
+ * The Bluedroid implementation only allows performing one GATT request at a time.
+ * As they are asynchronous anyway, we encapsulate every GATT request (read and write)
+ * inside a runnable action.
+ *
+ * These actions are then executed one after another, ensuring that every action's result
+ * has been posted before invoking the next action.
+ */
+public abstract class BtLEAction {
+ private final BluetoothGattCharacteristic characteristic;
+
+ public BtLEAction() {
+ this(null);
+ }
+
+ public BtLEAction(BluetoothGattCharacteristic characteristic) {
+ this.characteristic = characteristic;
+ }
+
+ public abstract boolean run(BluetoothGatt gatt);
+ /**
+ * Returns the GATT characteristic being read/written/...
+ * @return the GATT characteristic, or null
+ */
+ public BluetoothGattCharacteristic getCharacteristic() {
+ return characteristic;
+ }
+
+ public String toString() {
+ BluetoothGattCharacteristic characteristic = getCharacteristic();
+ String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
+ return getClass().getSimpleName() + " on characteristic: " + uuid;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEQueue.java
new file mode 100644
index 000000000..bbb42b81f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEQueue.java
@@ -0,0 +1,273 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import nodomain.freeyourgadget.gadgetbridge.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.GBDevice.State;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * One queue/thread per connectable device.
+ */
+public final class BtLEQueue {
+ private static final String TAG = BtLEQueue.class.getSimpleName();
+
+ private GBDevice mGbDevice;
+ private BluetoothAdapter mBluetoothAdapter;
+ private BluetoothGatt mBluetoothGatt;
+ private volatile BlockingQueue mTransactions = new LinkedBlockingQueue();
+ private volatile boolean mDisposed;
+ private volatile boolean mAbortTransaction;
+
+ private Context mContext;
+ private CountDownLatch mWaitForActionResultLatch;
+ private CountDownLatch mConnectionLatch;
+ private BluetoothGattCharacteristic mWaitCharacteristic;
+ private GattCallback mExternalGattCallback;
+
+ private Thread dispatchThread = new Thread("Bluetooth GATT Dispatcher") {
+
+ public void run() {
+ while (!mDisposed) {
+ try {
+ Transaction transaction = mTransactions.take();
+ if (!isConnected()) {
+ // TODO: request connection and initialization from the outside and wait until finished
+
+ // wait until the connection succeeds before running the actions
+ // Note that no automatic connection is performed. This has to be triggered
+ // on the outside typically by the DeviceSupport. The reason is that
+ // devices have different kinds of initializations and this class has no
+ // idea about them.
+ mConnectionLatch = new CountDownLatch(1);
+ mConnectionLatch.await();
+ mConnectionLatch = null;
+ }
+
+ mAbortTransaction = false;
+ // Run all actions of the transaction until one doesn't succeed
+ for (BtLEAction action : transaction.getActions()) {
+ mWaitCharacteristic = action.getCharacteristic();
+ if (action.run(mBluetoothGatt)) {
+ mWaitForActionResultLatch = new CountDownLatch(1);
+ mWaitForActionResultLatch.await();
+ mWaitForActionResultLatch = null;
+ if (mAbortTransaction) {
+ break;
+ }
+ } else {
+ Log.e(TAG, "Action returned false: " + action);
+ break; // abort the transaction
+ }
+ }
+ } catch (InterruptedException ignored) {
+ mWaitForActionResultLatch = null;
+ mConnectionLatch = null;
+ } finally {
+ mWaitCharacteristic = null;
+ }
+ }
+ Log.i(TAG, "Queue Dispatch Thread terminated.");
+ }
+ };
+
+ public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, Context context) {
+ mBluetoothAdapter = bluetoothAdapter;
+ mGbDevice = gbDevice;
+ mExternalGattCallback = externalGattCallback;
+ mContext = context;
+
+ dispatchThread.start();
+ }
+
+ protected boolean isConnected() {
+ return mGbDevice.isConnected();
+ }
+
+ /**
+ * Connects to the given remote device. Note that this does not perform any device
+ * specific initialization. This should be done in the specific {@link DeviceSupport}
+ * class.
+ *
+ * @return true whether the connection attempt was successfully triggered
+ */
+ public boolean connect() {
+ if (mBluetoothGatt != null) {
+ disconnect();
+ }
+ BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
+ mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
+ boolean result = mBluetoothGatt.connect();
+ setDeviceConnectionState(result ? State.CONNECTING : State.NOT_CONNECTED);
+ return result;
+ }
+
+ private void setDeviceConnectionState(State newState) {
+ mGbDevice.setState(newState);
+ mGbDevice.sendDeviceUpdateIntent(mContext);
+ if (mConnectionLatch != null) {
+ mConnectionLatch.countDown();
+ }
+ }
+
+ public void disconnect() {
+ if (mBluetoothGatt != null) {
+ Log.i(TAG, "Disconnecting BtLEQueue from GATT device");
+ mBluetoothGatt.disconnect();
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ }
+
+ private void handleDisconnected() {
+ mTransactions.clear();
+ if (mWaitForActionResultLatch != null) {
+ mWaitForActionResultLatch.countDown();
+ }
+ setDeviceConnectionState(State.NOT_CONNECTED);
+ }
+
+ public void dispose() {
+ if (mDisposed) {
+ return;
+ }
+ mDisposed = true;
+// try {
+ disconnect();
+ dispatchThread.interrupt();
+ dispatchThread = null;
+// dispatchThread.join();
+// } catch (InterruptedException ex) {
+// Log.e(TAG, "Exception while disposing BtLEQueue", ex);
+// }
+ }
+
+ /**
+ * Adds a transaction to the end of the queue.
+ * @param transaction
+ */
+ public void add(Transaction transaction) {
+ if (!transaction.isEmpty()) {
+ mTransactions.add(transaction);
+ }
+ }
+
+ public void clear() {
+ mTransactions.clear();
+ }
+
+ /**
+ * Retrieves a list of supported GATT services on the connected device. This should be
+ * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
+ *
+ * @return A {@code List} of supported services.
+ */
+ public List getSupportedGattServices() {
+ if (mBluetoothGatt == null) {
+ return Collections.emptyList();
+ }
+ return mBluetoothGatt.getServices();
+ }
+
+ // Implements callback methods for GATT events that the app cares about. For example,
+ // connection change and services discovered.
+ private final BluetoothGattCallback internalGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ switch (newState) {
+ case BluetoothProfile.STATE_CONNECTED:
+ Log.i(TAG, "Connected to GATT server.");
+ setDeviceConnectionState(State.CONNECTED);
+ // Attempts to discover services after successful connection.
+ Log.i(TAG, "Attempting to start service discovery:" +
+ mBluetoothGatt.discoverServices());
+ break;
+ case BluetoothProfile.STATE_DISCONNECTED:
+ Log.i(TAG, "Disconnected from GATT server.");
+ handleDisconnected();
+ break;
+ case BluetoothProfile.STATE_CONNECTING:
+ Log.i(TAG, "Connecting to GATT server...");
+ setDeviceConnectionState(State.CONNECTING);
+ break;
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ if (mExternalGattCallback != null) {
+ // only propagate the successful event
+ mExternalGattCallback.onServicesDiscovered(gatt);
+ }
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: " + status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Log.e(TAG, "Writing characteristic " + characteristic.getUuid() + " succeeded.");
+ } else {
+ Log.e(TAG, "Writing characteristic " + characteristic.getUuid() + " failed: "+ status);
+ }
+ if (mExternalGattCallback != null) {
+ mExternalGattCallback.onCharacteristicWrite(gatt, characteristic, status);
+ }
+ checkWaitingCharacteristic(characteristic, status);
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ if (status != BluetoothGatt.GATT_SUCCESS) {
+ Log.e(TAG, "Reading characteristic " + characteristic.getUuid() + " failed: " + status);
+ }
+ if (mExternalGattCallback != null) {
+ mExternalGattCallback.onCharacteristicRead(gatt, characteristic, status);
+ }
+ checkWaitingCharacteristic(characteristic, status);
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ if (mExternalGattCallback != null) {
+ mExternalGattCallback.onCharacteristicChanged(gatt, characteristic);
+ }
+ }
+
+ @Override
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+ if (mExternalGattCallback != null) {
+ mExternalGattCallback.onReadRemoteRssi(gatt, rssi, status);
+ }
+ }
+
+ private void checkWaitingCharacteristic(BluetoothGattCharacteristic characteristic, int status) {
+ if (status != BluetoothGatt.GATT_SUCCESS) {
+ mAbortTransaction = true;
+ }
+ if (characteristic != null && BtLEQueue.this.mWaitCharacteristic != null && characteristic.getUuid().equals(BtLEQueue.this.mWaitCharacteristic.getUuid())) {
+ if (mWaitForActionResultLatch != null) {
+ mWaitForActionResultLatch.countDown();
+ }
+ }
+ }
+ };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/GattCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/GattCallback.java
new file mode 100644
index 000000000..ce20371d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/GattCallback.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+/**
+ * Callback interface handling gatt events.
+ * Pretty much the same as {@link BluetoothGattCallback}, except it's an interface
+ * instead of an abstract class. Some handlers commented out, because not used (yet).
+ */
+public interface GattCallback {
+
+ /**
+ * @see BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)
+ * @param gatt
+ * @param status
+ * @param newState
+ */
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState);
+
+ /**
+ * @see BluetoothGattCallback#onServicesDiscovered(BluetoothGatt, int)
+ * @param gatt
+ */
+ public void onServicesDiscovered(BluetoothGatt gatt);
+
+ /**
+ * @see BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, BluetoothGattCharacteristic, int)
+ * @param gatt
+ * @param characteristic
+ * @param status
+ */
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status);
+
+ /**
+ * @see BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)
+ * @param gatt
+ * @param characteristic
+ * @param status
+ */
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status);
+
+ /**
+ * @see BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, BluetoothGattCharacteristic)
+ * @param gatt
+ * @param characteristic
+ */
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic);
+
+// /**
+// * @see BluetoothGattCallback#onDescriptorRead(BluetoothGatt, BluetoothGattDescriptor, int)
+// * @param gatt
+// * @param descriptor
+// * @param status
+// */
+// public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+// int status);
+//
+// /**
+// * @see BluetoothGattCallback#onDescriptorWrite(BluetoothGatt, BluetoothGattDescriptor, int)
+// * @param gatt
+// * @param descriptor
+// * @param status
+// */
+// public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+// int status);
+//
+// /**
+// * @see BluetoothGattCallback#onReliableWriteCompleted(BluetoothGatt, int)
+// * @param gatt
+// * @param status
+// */
+// public void onReliableWriteCompleted(BluetoothGatt gatt, int status);
+
+ /**
+ * @see BluetoothGattCallback#onReadRemoteRssi(BluetoothGatt, int, int)
+ * @param gatt
+ * @param rssi
+ * @param status
+ */
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status);
+
+// /**
+// * @see BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)
+// * @param gatt
+// * @param mtu
+// * @param status
+// */
+// public void onMtuChanged(BluetoothGatt gatt, int mtu, int status);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/ReadAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/ReadAction.java
new file mode 100644
index 000000000..598bbb4b7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/ReadAction.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+/**
+ * Invokes a read operation on a given GATT characteristic.
+ * The result will be made available asynchronously through the
+ * {@link BluetoothGattCallback}
+ */
+public class ReadAction extends BtLEAction {
+
+ public ReadAction(BluetoothGattCharacteristic characteristic) {
+ super(characteristic);
+ }
+
+ @Override
+ public boolean run(BluetoothGatt gatt) {
+ return gatt.readCharacteristic(getCharacteristic());
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/Transaction.java
new file mode 100644
index 000000000..8c4c4ce63
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/Transaction.java
@@ -0,0 +1,42 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Groups a bunch of {@link BtLEAction actions} together, making sure
+ * that upon failure of one action, all subsequent actions are discarded.
+ *
+ * @author TREND
+ */
+public class Transaction {
+ private String mName;
+ private List mActions = new ArrayList<>(4);
+
+ public Transaction(String taskName) {
+ this.mName = taskName;
+ }
+
+ public String getTaskName() {
+ return mName;
+ }
+
+ public void add(BtLEAction action) {
+ mActions.add(action);
+ }
+
+ public List getActions() {
+ return Collections.unmodifiableList(mActions);
+ }
+
+ public boolean isEmpty() {
+ return mActions.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.US, "Transaction task: %s with %d actions", getTaskName(), mActions.size());
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/TransactionBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/TransactionBuilder.java
new file mode 100644
index 000000000..40ae8e0e6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/TransactionBuilder.java
@@ -0,0 +1,54 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.util.Log;
+
+public class TransactionBuilder {
+ private static final String TAG = TransactionBuilder.class.getSimpleName();
+
+ private Transaction mTransaction;
+
+ public TransactionBuilder(String taskName) {
+ mTransaction = new Transaction(taskName);
+ }
+
+ public TransactionBuilder read(BluetoothGattCharacteristic characteristic) {
+ if (characteristic == null) {
+ Log.w(TAG, "Unable to read characteristic: null");
+ return this;
+ }
+ ReadAction action = new ReadAction(characteristic);
+ return add(action);
+ }
+
+ public TransactionBuilder write(BluetoothGattCharacteristic characteristic, byte[] data) {
+ if (characteristic == null) {
+ Log.w(TAG, "Unable to write characteristic: null");
+ return this;
+ }
+ WriteAction action = new WriteAction(characteristic, data);
+ return add(action);
+ }
+
+ public TransactionBuilder wait(int millis) {
+ WaitAction action = new WaitAction(millis);
+ return add(action);
+ }
+
+ public TransactionBuilder add(BtLEAction action) {
+ mTransaction.add(action);
+ return this;
+ }
+
+ /**
+ * To be used as the final step to execute the transaction by the given queue.
+ * @param queue
+ */
+ public void queue(BtLEQueue queue) {
+ queue.add(mTransaction);
+ }
+
+ public Transaction getTransaction() {
+ return mTransaction;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java
new file mode 100644
index 000000000..b0cf424dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import android.bluetooth.BluetoothGatt;
+
+public class WaitAction extends BtLEAction {
+
+ private int mMillis;
+
+ public WaitAction(int millis) {
+ mMillis = millis;
+ }
+
+ @Override
+ public boolean run(BluetoothGatt gatt) {
+ try {
+ Thread.sleep(mMillis);
+ return true;
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WriteAction.java
new file mode 100644
index 000000000..4925a8ace
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WriteAction.java
@@ -0,0 +1,28 @@
+package nodomain.freeyourgadget.gadgetbridge.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+/**
+ * Invokes a write operation on a given GATT characteristic.
+ * The result status will be made available asynchronously through the
+ * {@link BluetoothGattCallback}
+ */
+public class WriteAction extends BtLEAction {
+
+ private byte[] value;
+
+ public WriteAction(BluetoothGattCharacteristic characteristic, byte[] value) {
+ super(characteristic);
+ this.value = value;
+ }
+
+ @Override
+ public boolean run(BluetoothGatt gatt) {
+ if (getCharacteristic().setValue(value)) {
+ return gatt.writeCharacteristic(getCharacteristic());
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java
new file mode 100644
index 000000000..6d11bcfdd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java
@@ -0,0 +1,271 @@
+package nodomain.freeyourgadget.gadgetbridge.miband;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class MiBandService {
+
+ public static final String MAC_ADDRESS_FILTER = "88:0F:10";
+
+ public static final String BASE_UUID = "0000%s-0000-1000-8000-00805f9b34fb";
+
+ public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
+
+ public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01"));
+
+ public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = UUID.fromString(String.format(BASE_UUID, "FF02"));
+
+ public static final UUID UUID_CHARACTERISTIC_NOTIFICATION = UUID.fromString(String.format(BASE_UUID, "FF03"));
+
+ public static final UUID UUID_CHARACTERISTIC_USER_INFO = UUID.fromString(String.format(BASE_UUID, "FF04"));
+
+ public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID, "FF05"));
+
+ public static final UUID UUID_CHARACTERISTIC_REALTIME_STEPS = UUID.fromString(String.format(BASE_UUID, "FF06"));
+
+ public static final UUID UUID_CHARACTERISTIC_ACTIVITY_DATA = UUID.fromString(String.format(BASE_UUID, "FF07"));
+
+ public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString(String.format(BASE_UUID, "FF08"));
+
+ public static final UUID UUID_CHARACTERISTIC_LE_PARAMS = UUID.fromString(String.format(BASE_UUID, "FF09"));
+
+ public static final UUID UUID_CHARACTERISTIC_DATE_TIME = UUID.fromString(String.format(BASE_UUID, "FF0A"));
+
+ public static final UUID UUID_CHARACTERISTIC_STATISTICS = UUID.fromString(String.format(BASE_UUID, "FF0B"));
+
+ public static final UUID UUID_CHARACTERISTIC_BATTERY = UUID.fromString(String.format(BASE_UUID, "FF0C"));
+
+ public static final UUID UUID_CHARACTERISTIC_TEST = UUID.fromString(String.format(BASE_UUID, "FF0D"));
+
+ public static final UUID UUID_CHARACTERISTIC_SENSOR_DATA = UUID.fromString(String.format(BASE_UUID, "FF0E"));
+
+ public static final UUID UUID_CHARACTERISTIC_PAIR = UUID.fromString(String.format(BASE_UUID, "FF0F"));
+
+ public static final byte ALIAS_LEN = 0xa;
+
+ public static final byte NOTIFY_AUTHENTICATION_FAILED = 0x6;
+
+ public static final byte NOTIFY_AUTHENTICATION_SUCCESS = 0x5;
+
+ public static final byte NOTIFY_CONN_PARAM_UPDATE_FAILED = 0x3;
+
+ public static final byte NOTIFY_CONN_PARAM_UPDATE_SUCCESS = 0x4;
+
+ public static final int NOTIFY_DEVICE_MALFUNCTION = 0xff;
+
+ public static final byte NOTIFY_FIRMWARE_UPDATE_FAILED = 0x1;
+
+ public static final byte NOTIFY_FIRMWARE_UPDATE_SUCCESS = 0x2;
+
+ public static final byte NOTIFY_FITNESS_GOAL_ACHIEVED = 0x7;
+
+ public static final byte NOTIFY_FW_CHECK_FAILED = 0xb;
+
+ public static final byte NOTIFY_FW_CHECK_SUCCESS = 0xc;
+
+ public static final byte NOTIFY_NORMAL = 0x0;
+
+ public static final int NOTIFY_PAIR_CANCEL = 0xef;
+
+ public static final byte NOTIFY_RESET_AUTHENTICATION_FAILED = 0x9;
+
+ public static final byte NOTIFY_RESET_AUTHENTICATION_SUCCESS = 0xa;
+
+ public static final byte NOTIFY_SET_LATENCY_SUCCESS = 0x8;
+
+ public static final byte NOTIFY_STATUS_MOTOR_ALARM = 0x11;
+
+ public static final byte NOTIFY_STATUS_MOTOR_AUTH = 0x13;
+
+ public static final byte NOTIFY_STATUS_MOTOR_AUTH_SUCCESS = 0x15;
+
+ public static final byte NOTIFY_STATUS_MOTOR_CALL = 0xe;
+
+ public static final byte NOTIFY_STATUS_MOTOR_DISCONNECT = 0xf;
+
+ public static final byte NOTIFY_STATUS_MOTOR_GOAL = 0x12;
+
+ public static final byte NOTIFY_STATUS_MOTOR_NOTIFY = 0xd;
+
+ public static final byte NOTIFY_STATUS_MOTOR_SHUTDOWN = 0x14;
+
+ public static final byte NOTIFY_STATUS_MOTOR_SMART_ALARM = 0x10;
+
+ public static final byte NOTIFY_STATUS_MOTOR_TEST = 0x16;
+
+ public static final byte NOTIFY_UNKNOWN = -0x1;
+
+ public static final String UUID_CHARACTERISTIC_FEATURE = "2A9E";
+
+ public static final String UUID_CHARACTERISTIC_MEASUREMENT = "2A9D";
+
+ public static final String UUID_SERVICE_WEIGHT_SCALE_SERVICE = "181D";
+
+ public static final String UUID_SERVICE_WEIGHT_SERVICE = "00001530-0000-3512-2118-0009af100700";
+
+ public static final byte MSG_CONNECTED = 0x0;
+
+ public static final byte MSG_DISCONNECTED = 0x1;
+
+ public static final byte MSG_CONNECTION_FAILED = 0x2;
+
+ public static final byte MSG_INITIALIZATION_FAILED = 0x3;
+
+ public static final byte MSG_INITIALIZATION_SUCCESS = 0x4;
+
+ public static final byte MSG_STEPS_CHANGED = 0x5;
+
+ public static final byte MSG_DEVICE_STATUS_CHANGED = 0x6;
+
+ public static final byte MSG_BATTERY_STATUS_CHANGED = 0x7;
+
+ /*
+
+ public static final COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xat;
+
+ public static final byte COMMAND_FACTORY_RESET = 0x9t;
+
+ public static final byte COMMAND_FETCH_DATA = 0x6t;
+
+ public static final byte COMMAND_GET_SENSOR_DATA = 0x12t
+
+ public static final byte COMMAND_REBOOT = 0xct
+
+ public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7t
+
+ public static final COMMAND_SEND_NOTIFICATION = 0x8t
+
+ public static final int COMMAND_SET_COLOR_THEME = et;
+
+ public static final COMMAND_SET_FITNESS_GOAL = 0x5t
+
+ public static final COMMAND_SET_REALTIME_STEP = 0x10t
+
+ public static final COMMAND_SET_REALTIME_STEPS_NOTIFICATION = 0x3t
+
+ public static final COMMAND_SET_TIMER = 0x4t
+
+ public static final COMMAND_SET_WEAR_LOCATION = 0xft
+
+ public static final COMMAND_STOP_MOTOR_VIBRATE = 0x13t
+
+ public static final COMMAND_STOP_SYNC_DATA = 0x11t
+
+ public static final COMMAND_SYNC = 0xbt
+
+ public static final CONNECTION_LATENCY_LEVEL_HIGH = 0x2t;
+
+ public static final CONNECTION_LATENCY_LEVEL_LOW = 0x0t;
+
+ public static final CONNECTION_LATENCY_LEVEL_MEDIUM = 0x1t;
+
+ public static final MODE_REGULAR_DATA_LEN_BYTE = 0x0t;
+
+ public static final MODE_REGULAR_DATA_LEN_MINITE = 0x1t
+
+ public static final PROFILE_STATE_AUTHENTICATION_FAILED:I = 0x4
+
+ public static final PROFILE_STATE_AUTHENTICATION_SUCCESS:I = 0x3
+
+ public static final PROFILE_STATE_INITIALIZATION_FAILED:I = 0x2
+
+ public static final PROFILE_STATE_INITIALIZATION_SUCCESS:I = 0x1
+
+ public static final PROFILE_STATE_UNKNOWN:I = 0x0
+
+ public static final TEST_DISCONNECTED_REMINDER = 0x5t
+
+ public static final TEST_NOTIFICATION = 0x3t
+
+ public static final TEST_REMOTE_DISCONNECT = 0x1t
+
+ public static final TEST_SELFTEST = 0x2t
+
+ */
+
+ private static Map MIBAND_DEBUG;
+
+ static {
+ MIBAND_DEBUG = new HashMap<>();
+ MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
+
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_INFO, "Device Info");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_NAME, "Device Name");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_NOTIFICATION, "Notification");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_USER_INFO, "User Info");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_CONTROL_POINT, "Control Point");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_REALTIME_STEPS, "Realtime Steps");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_ACTIVITY_DATA, "Activity Data");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_FIRMWARE_DATA, "Firmware Data");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_LE_PARAMS, "LE Params");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DATE_TIME, "Date/Time");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_STATISTICS, "Statistics");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_BATTERY, "Battery");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_TEST, "Test");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_SENSOR_DATA, "Sensor Data");
+ MIBAND_DEBUG.put(UUID_CHARACTERISTIC_PAIR, "Pair");
+
+ // extra:
+ MIBAND_DEBUG.put(UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"), "Generic Access Service");
+ MIBAND_DEBUG.put(UUID.fromString("00001801-0000-1000-8000-00805f9b34fb"), "Generic Attribute Service");
+ MIBAND_DEBUG.put(UUID.fromString("00002a43-0000-1000-8000-00805f9b34fb"), "Alert Category ID");
+ MIBAND_DEBUG.put(UUID.fromString("00002a42-0000-1000-8000-00805f9b34fb"), "Alert Category ID Bit Mask");
+ MIBAND_DEBUG.put(UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"), "Alert Level");
+ MIBAND_DEBUG.put(UUID.fromString("00002a44-0000-1000-8000-00805f9b34fb"), "Alert Notification Control Point");
+ MIBAND_DEBUG.put(UUID.fromString("00002a3f-0000-1000-8000-00805f9b34fb"), "Alert Status");
+ MIBAND_DEBUG.put(UUID.fromString("00002a01-0000-1000-8000-00805f9b34fb"), "Appearance");
+ MIBAND_DEBUG.put(UUID.fromString("00002a49-0000-1000-8000-00805f9b34fb"), "Blood Pressure Feature");
+ MIBAND_DEBUG.put(UUID.fromString("00002a35-0000-1000-8000-00805f9b34fb"), "Blood Pressure Measurement");
+ MIBAND_DEBUG.put(UUID.fromString("00002a38-0000-1000-8000-00805f9b34fb"), "Body Sensor Location");
+ MIBAND_DEBUG.put(UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb"), "Current Time");
+ MIBAND_DEBUG.put(UUID.fromString("00002a08-0000-1000-8000-00805f9b34fb"), "Date Time");
+ MIBAND_DEBUG.put(UUID.fromString("00002a0a-0000-1000-8000-00805f9b34fb"), "Day Date Time");
+ MIBAND_DEBUG.put(UUID.fromString("00002a09-0000-1000-8000-00805f9b34fb"), "Day of Week");
+ MIBAND_DEBUG.put(UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb"), "Device Name");
+ MIBAND_DEBUG.put(UUID.fromString("00002a0d-0000-1000-8000-00805f9b34fb"), "DST Offset");
+ MIBAND_DEBUG.put(UUID.fromString("00002a0c-0000-1000-8000-00805f9b34fb"), "Exact Time 256");
+ MIBAND_DEBUG.put(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb"), "Firmware Revision String");
+ MIBAND_DEBUG.put(UUID.fromString("00002a27-0000-1000-8000-00805f9b34fb"), "Hardware Revision String");
+ MIBAND_DEBUG.put(UUID.fromString("00002a39-0000-1000-8000-00805f9b34fb"), "Heart Rate Control Point");
+ MIBAND_DEBUG.put(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb"), "Heart Rate Measurement");
+ MIBAND_DEBUG.put(UUID.fromString("00002a2a-0000-1000-8000-00805f9b34fb"), "IEEE 11073-20601 Regulatory");
+ MIBAND_DEBUG.put(UUID.fromString("00002a36-0000-1000-8000-00805f9b34fb"), "Intermediate Cuff Pressure");
+ MIBAND_DEBUG.put(UUID.fromString("00002a1e-0000-1000-8000-00805f9b34fb"), "Intermediate Temperature");
+ MIBAND_DEBUG.put(UUID.fromString("00002a0f-0000-1000-8000-00805f9b34fb"), "Local Time Information");
+ MIBAND_DEBUG.put(UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb"), "Manufacturer Name String");
+ MIBAND_DEBUG.put(UUID.fromString("00002a21-0000-1000-8000-00805f9b34fb"), "Measurement Interval");
+ MIBAND_DEBUG.put(UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb"), "Model Number String");
+ MIBAND_DEBUG.put(UUID.fromString("00002a46-0000-1000-8000-00805f9b34fb"), "New Alert");
+ MIBAND_DEBUG.put(UUID.fromString("00002a04-0000-1000-8000-00805f9b34fb"), "Peripheral Preferred Connection Parameters");
+ MIBAND_DEBUG.put(UUID.fromString("00002a02-0000-1000-8000-00805f9b34fb"), "Peripheral Privacy Flag");
+ MIBAND_DEBUG.put(UUID.fromString("00002a03-0000-1000-8000-00805f9b34fb"), "Reconnection Address");
+ MIBAND_DEBUG.put(UUID.fromString("00002a14-0000-1000-8000-00805f9b34fb"), "Reference Time Information");
+ MIBAND_DEBUG.put(UUID.fromString("00002a40-0000-1000-8000-00805f9b34fb"), "Ringer Control Point");
+ MIBAND_DEBUG.put(UUID.fromString("00002a41-0000-1000-8000-00805f9b34fb"), "Ringer Setting");
+ MIBAND_DEBUG.put(UUID.fromString("00002a25-0000-1000-8000-00805f9b34fb"), "Serial Number String");
+ MIBAND_DEBUG.put(UUID.fromString("00002a05-0000-1000-8000-00805f9b34fb"), "Service Changed");
+ MIBAND_DEBUG.put(UUID.fromString("00002a28-0000-1000-8000-00805f9b34fb"), "Software Revision String");
+ MIBAND_DEBUG.put(UUID.fromString("00002a47-0000-1000-8000-00805f9b34fb"), "Supported New Alert Category");
+ MIBAND_DEBUG.put(UUID.fromString("00002a48-0000-1000-8000-00805f9b34fb"), "Supported Unread Alert Category");
+ MIBAND_DEBUG.put(UUID.fromString("00002a23-0000-1000-8000-00805f9b34fb"), "System ID");
+ MIBAND_DEBUG.put(UUID.fromString("00002a1c-0000-1000-8000-00805f9b34fb"), "Temperature Measurement");
+ MIBAND_DEBUG.put(UUID.fromString("00002a1d-0000-1000-8000-00805f9b34fb"), "Temperature Type");
+ MIBAND_DEBUG.put(UUID.fromString("00002a12-0000-1000-8000-00805f9b34fb"), "Time Accuracy");
+ MIBAND_DEBUG.put(UUID.fromString("00002a13-0000-1000-8000-00805f9b34fb"), "Time Source");
+ MIBAND_DEBUG.put(UUID.fromString("00002a16-0000-1000-8000-00805f9b34fb"), "Time Update Control Point");
+ MIBAND_DEBUG.put(UUID.fromString("00002a17-0000-1000-8000-00805f9b34fb"), "Time Update State");
+ MIBAND_DEBUG.put(UUID.fromString("00002a11-0000-1000-8000-00805f9b34fb"), "Time with DST");
+ MIBAND_DEBUG.put(UUID.fromString("00002a0e-0000-1000-8000-00805f9b34fb"), "Time Zone");
+ MIBAND_DEBUG.put(UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb"), "Tx Power Level");
+ MIBAND_DEBUG.put(UUID.fromString("00002a45-0000-1000-8000-00805f9b34fb"), "Unread Alert Status");
+ }
+
+ public static String lookup(UUID uuid, String fallback) {
+ String name = MIBAND_DEBUG.get(uuid);
+ if (name == null) {
+ name = fallback;
+ }
+ return name;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
index 12c6661d9..f652d64ac 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
@@ -1,32 +1,120 @@
package nodomain.freeyourgadget.gadgetbridge.miband;
-import nodomain.freeyourgadget.gadgetbridge.AbstractBTLEDeviceSupport;
+import java.io.IOException;
+import java.util.UUID;
+
import nodomain.freeyourgadget.gadgetbridge.GBCommand;
+import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.util.Log;
public class MiBandSupport extends AbstractBTLEDeviceSupport {
- @Override
- public void dispose() {
- // TODO Auto-generated method stub
+ private static final String TAG = MiBandSupport.class.getSimpleName();
+ public MiBandSupport() {
+ addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
}
@Override
- public boolean connect() {
- // TODO Auto-generated method stub
- return false;
+ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+ pair(builder).sendUserInfo(builder);
+ return builder;
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return true;
+ }
+
+ private byte[] getDefaultNotification() {
+ final int vibrateTimes = 1;
+ final long vibrateDuration = 250l;
+ final int flashTimes = 1;
+ final int flashColour = 0xFFFFFFFF;
+ final int originalColour = 0xFFFFFFFF;
+ final long flashDuration = 250l;
+
+ return getNotification(vibrateDuration, vibrateTimes, flashTimes, flashColour, originalColour, flashDuration);
+ }
+
+ private void sendDefaultNotification(TransactionBuilder builder) {
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+ Log.i(TAG, "Sending notification to MiBand: " + characteristic);
+ builder.write(characteristic, getDefaultNotification()).queue(getQueue());
+ }
+
+ private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) {
+ byte[] vibrate = new byte[]{ (byte) 8, (byte) 1 };
+ byte r = 6;
+ byte g = 0;
+ byte b = 6;
+ boolean display = true;
+ // byte[] flashColor = new byte[]{ 14, r, g, b, display ? (byte) 1 : (byte) 0 };
+ return vibrate;
+ }
+
+ private UserInfo getUserInfo() {
+ // SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.getApplicationContext());
+ // UserInfo mInfo = new UserInfo(
+ // mSharedPreferences.getString(MiBandConstants.PREFERENCE_MAC_ADDRESS, ""),
+ // "1550050550",
+ // (mSharedPreferences.getString(MiBandConstants.PREFERENCE_GENDER, "Male") == "Male") ? 1 : 0,
+ // Integer.parseInt(mSharedPreferences.getString(MiBandConstants.PREFERENCE_AGE, "25")),
+ // Integer.parseInt(mSharedPreferences.getString(MiBandConstants.PREFERENCE_HEIGHT, "175")),
+ // Integer.parseInt(mSharedPreferences.getString(MiBandConstants.PREFERENCE_WEIGHT, "60")),
+ // 0
+ // );
+ return UserInfo.getDefault(getDevice().getAddress());
+ }
+
+ /**
+ * Part of device initialization process. Do not call manually.
+ * @param builder
+ * @return
+ */
+ private MiBandSupport sendUserInfo(TransactionBuilder builder) {
+ Log.d(TAG, "Writing User Info!");
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_USER_INFO);
+ builder.write(characteristic, getUserInfo().getData());
+ return this;
+ }
+
+ /**
+ * Part of device initialization process. Do not call manually.
+ * @param builder
+ * @return
+ */
+ private MiBandSupport pair(TransactionBuilder transaction) {
+ Log.i(TAG, "Attempting to pair MI device...");
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_PAIR);
+ if (characteristic != null) {
+ transaction.write(characteristic, new byte[]{2});
+ } else {
+ Log.i(TAG, "Unable to pair MI device -- characteristic not available");
+ }
+ return this;
+ }
+
+ private void performDefaultNotification(String task) {
+ try {
+ TransactionBuilder builder = performInitialized(task);
+ sendDefaultNotification(builder);
+ } catch (IOException ex) {
+ Log.e(TAG, "Unable to send notification to MI device", ex);
+ }
}
@Override
public void onSMS(String from, String body) {
- // TODO Auto-generated method stub
-
+ performDefaultNotification("sms received");
}
@Override
public void onEmail(String from, String subject, String body) {
- // TODO Auto-generated method stub
-
+ performDefaultNotification("email received");
}
@Override
@@ -70,4 +158,39 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
// TODO Auto-generated method stub
}
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ UUID characteristicUUID = characteristic.getUuid();
+ if (MiBandService.UUID_CHARACTERISTIC_PAIR.equals(characteristicUUID)) {
+ handlePairResult(characteristic.getValue(), status);
+ }
+ }
+
+ private void handlePairResult(byte[] pairResult, int status) {
+ if (status != BluetoothGatt.GATT_SUCCESS) {
+ Log.i(TAG, "Pairing MI device failed: " + status);
+ return;
+ }
+
+ Object value = null;
+ if (pairResult != null) {
+ if (pairResult.length == 1) {
+ try {
+ byte b = pairResult[0];
+ Integer intValue = Integer.valueOf((int) b);
+ if (intValue.intValue() == 2) {
+ Log.i(TAG, "Successfully paired MI device");
+ return;
+ }
+ } catch (Exception ex) {
+ Log.w(TAG, "Error identifying pairing result", ex);
+ return;
+ }
+ }
+ value = pairResult.toString();
+ }
+ Log.i(TAG, "MI Band pairing result: " + value);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/UserInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/UserInfo.java
new file mode 100644
index 000000000..dbca48040
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/UserInfo.java
@@ -0,0 +1,91 @@
+package nodomain.freeyourgadget.gadgetbridge.miband;
+
+/**
+ * Created by UgoRaffaele on 30/01/2015.
+ */
+public class UserInfo {
+
+ private String btAddress;
+ private String alias;
+ private int gender;
+ private int age;
+ private int height;
+ private int weight;
+ private int type;
+
+ private byte[] data = new byte[20];
+
+ /**
+ * Creates a default user info.
+ * @param btAddress the address of the MI Band to connect to.
+ */
+ public static UserInfo getDefault(String btAddress) {
+ return new UserInfo(btAddress, "1550050550", 0, 25, 175, 70, 0);
+ }
+
+ /**
+ * Creates a user info with the given data
+ * @param address the address of the MI Band to connect to.
+ */
+ public UserInfo(String address, String alias, int gender, int age, int height, int weight, int type) {
+
+ this.btAddress = address;
+ this.alias = alias;
+ this.gender = gender;
+ this.age = age;
+ this.height = height;
+ this.weight = weight;
+ this.type = type;
+
+ byte[] sequence = new byte[20];
+
+ int uid = Integer.parseInt(alias);
+
+ sequence[0] = (byte) uid;
+ sequence[1] = (byte) (uid >>> 8);
+ sequence[2] = (byte) (uid >>> 16);
+ sequence[3] = (byte) (uid >>> 24);
+
+ sequence[4] = (byte) (gender & 0xff);
+ sequence[5] = (byte) (age & 0xff);
+ sequence[6] = (byte) (height & 0xff);
+ sequence[7] = (byte) (weight & 0xff);
+ sequence[8] = (byte) (type & 0xff);
+
+ for (int u = 9; u < 19; u++)
+ sequence[u] = alias.getBytes()[u-9];
+
+ byte[] crcSequence = new byte[19];
+ for (int u = 0; u < crcSequence.length; u++)
+ crcSequence[u] = sequence[u];
+
+ sequence[19] = (byte) ((getCRC8(crcSequence) ^ Integer.parseInt(address.substring(address.length() - 2), 16)) & 0xff);
+
+ this.data = sequence;
+ }
+
+ public byte[] getData() {
+ return this.data;
+ }
+
+ protected int getCRC8(byte[] seq) {
+ int len = seq.length;
+ int i = 0;
+ byte crc = 0x00;
+
+ while (len-- > 0) {
+ byte extract = seq[i++];
+ for (byte tempI = 8; tempI != 0; tempI--) {
+ byte sum = (byte) ((crc & 0xff) ^ (extract & 0xff));
+ sum = (byte) ((sum & 0xff) & 0x01);
+ crc = (byte) ((crc & 0xff) >>> 1);
+ if (sum != 0) {
+ crc = (byte)((crc & 0xff) ^ 0x8c);
+ }
+ extract = (byte) ((extract & 0xff) >>> 1);
+ }
+ }
+ return (crc & 0xff);
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleSupport.java
index d2ad502ff..c49e7ea44 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleSupport.java
@@ -25,4 +25,9 @@ public class PebbleSupport extends AbstractBTDeviceSupport {
protected GBDeviceIoThread createDeviceIOThread() {
return new PebbleIoThread(getDevice(), getDeviceProtocol(), getBluetoothAdapter(), getContext());
}
+
+ @Override
+ public boolean useAutoConnect() {
+ return false;
+ }
}
diff --git a/app/src/main/lint.xml b/app/src/main/lint.xml
new file mode 100644
index 000000000..ee0eead5b
--- /dev/null
+++ b/app/src/main/lint.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/project.properties b/app/src/main/project.properties
new file mode 100644
index 000000000..7ad4d39c9
--- /dev/null
+++ b/app/src/main/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-21
+android.library.reference.1=../../../../android-sdk/extras/android/support/v7/appcompat