/*
* Copyright (C) 2014 OMRON Corporation
*
* 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 omron.HVC;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.UUID;
import org.deviceconnect.android.deviceplugin.hvc.BuildConfig;
/**
* Service for managing connection and data communication with a GATT server hosted on a
* given Bluetooth LE device.
*/
public class BleDeviceService {
/**
* DebugLog.
*/
static final boolean DEBUG_LOG = BuildConfig.DEBUG;
/** status. */
public static final int STATE_DISCONNECTED = 0;
/** status. */
public static final int STATE_CONNECTING = 1;
/** status. */
public static final int STATE_CONNECTED = 2;
/** action. */
public static final String ACTION_GATT_CONNECTED = "ACTION_GATT_CONNECTED";
/** action. */
public static final String ACTION_GATT_DISCONNECTED = "ACTION_GATT_DISCONNECTED";
/** action. */
public static final String ACTION_GATT_SERVICES_DISCOVERED = "ACTION_GATT_SERVICES_DISCOVERED";
/** action. */
public static final String ACTION_DATA_AVAILABLE = "ACTION_DATA_AVAILABLE";
/** data. */
public static final String NAME_DATA = ":NAME_DATA";
/** data. */
public static final String EXTRA_DATA = ":EXTRA_DATA";
/** value. */
public static final String DEVICE_DOES_NOT_SUPPORT_UART = "DEVICE_DOES_NOT_SUPPORT_UART";
/** CCCD. */
public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
/** UUID2. */
public static final UUID RX_SERVICE_UUID2 = UUID.fromString("35100001-d13a-4f39-8ab3-bf64d4fbb4b4");
/** UUID2. */
public static final UUID RX_CHAR_UUID2 = UUID.fromString("35100002-d13a-4f39-8ab3-bf64d4fbb4b4");
/** UUID2. */
public static final UUID TX_CHAR_UUID2 = UUID.fromString("35100003-d13a-4f39-8ab3-bf64d4fbb4b4");
/** UUID. */
public static final UUID NAME_CHAR_UUID = UUID.fromString("35100004-d13a-4f39-8ab3-bf64d4fbb4b4");
/** BleCallback. */
private BleCallback mCallBack = null;
/** BluetoothGatt. */
private BluetoothGatt mBluetoothGatt;
/** BluetoothDeviceAddress. */
private String mBluetoothDeviceAddress;
/** ConnectionState. */
private int mConnectionState = STATE_DISCONNECTED;
/** TAG. */
private static final String TAG = "BleDeviceService";
/**
* Implements callback methods for GATT events that the app cares about. For example,
* connection change and services discovered.
*/
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
setmConnectionState(STATE_CONNECTED);
broadcastUpdate(intentAction);
if (DEBUG_LOG) {
Log.i(TAG, "Connected to GATT server.");
}
// Attempts to discover services after successful connection.
boolean result = mBluetoothGatt.discoverServices();
if (DEBUG_LOG) {
Log.i(TAG, "Attempting to start service discovery:"
+ result);
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
setmConnectionState(STATE_DISCONNECTED);
if (DEBUG_LOG) {
Log.i(TAG, "Disconnected from GATT server.");
}
broadcastUpdate(intentAction);
}
}
@Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (DEBUG_LOG) {
Log.w(TAG, "mBluetoothGatt = " + mBluetoothGatt);
}
BluetoothGattCharacteristic txChar;
BluetoothGattService rxService = mBluetoothGatt.getService(RX_SERVICE_UUID2);
if (rxService == null) {
showMessage("mBluetoothGatt null" + mBluetoothGatt);
showMessage("Rx service not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
txChar = rxService.getCharacteristic(TX_CHAR_UUID2);
if (txChar == null) {
showMessage("Rx charateristic not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
if (DEBUG_LOG) {
Log.w(TAG, "RxChar = " + TX_CHAR_UUID2.toString());
}
mBluetoothGatt.setCharacteristicNotification(txChar, true);
BluetoothGattDescriptor descriptor = txChar.getDescriptor(CCCD);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
if (DEBUG_LOG) {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
}
@Override
public void onCharacteristicRead(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic,
final int status) {
if (DEBUG_LOG) {
Log.d(TAG, String.format("onCharacteristicRead: %s", characteristic.getUuid()));
}
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic) {
if (DEBUG_LOG) {
Log.d(TAG, String.format("onCharacteristicChanged: %s", characteristic.getUuid()));
}
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
/**
* broadcastUpdate.
* @param action action
*/
private void broadcastUpdate(final String action) {
mCallBack.callbackMethod(action);
}
/**
* broadcastUpdate.
* @param action action
* @param characteristic characteristic
*/
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
// This is special handling for the Heart Rate Measurement profile. Data parsing is
// carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/
// Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
if (TX_CHAR_UUID2.equals(characteristic.getUuid())) {
if (DEBUG_LOG) {
Log.d(TAG, String.format("Received Text: %d", characteristic.getValue().length));
}
mCallBack.callbackMethod(action + EXTRA_DATA, characteristic.getValue());
} else if (NAME_CHAR_UUID.equals(characteristic.getUuid())) {
mCallBack.callbackMethod(action + NAME_DATA, characteristic.getValue());
}
}
/**
* Constructor.
* @param gattCallback gattCallback
*/
public BleDeviceService(final BleCallback gattCallback) {
mCallBack = gattCallback;
}
/**
* refreshDeviceCache.
* @param gatt gatt
* @return result
*/
private boolean refreshDeviceCache(final BluetoothGatt gatt) {
try {
BluetoothGatt localBluetoothGatt = gatt;
Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]);
if (localMethod != null) {
boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue();
if (DEBUG_LOG) {
Log.d(TAG, String.format("refreshDeviceCache : %b", bool));
}
return bool;
}
} catch (Exception localException) {
if (DEBUG_LOG) {
Log.e(TAG, "An exception occured while refreshing device");
}
}
if (DEBUG_LOG) {
Log.d(TAG, String.format("refreshDeviceCache : false"));
}
return false;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param context context
* @param device The device address of the destination device.
*
* @return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final Context context, final BluetoothDevice device) {
if (device == null) {
if (DEBUG_LOG) {
Log.w(TAG, "Device not found. Unable to connect.");
}
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && device.getAddress().equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
if (DEBUG_LOG) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
}
if (mBluetoothGatt.connect()) {
setmConnectionState(STATE_CONNECTING);
return true;
} else {
return false;
}
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
refreshDeviceCache(mBluetoothGatt);
if (DEBUG_LOG) {
Log.d(TAG, "Trying to create a new connection.");
}
mBluetoothDeviceAddress = device.getAddress();
setmConnectionState(STATE_CONNECTING);
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothGatt == null) {
if (DEBUG_LOG) {
Log.w(TAG, "mBluetoothGatt not initialized");
}
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
if (DEBUG_LOG) {
Log.w(TAG, "mBluetoothGatt closed");
}
mBluetoothDeviceAddress = null;
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the
* {@code BluetoothGattCallback#onCharacteristicRead
* (android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* @param characteristic The characteristic to read from.
*/
public void readCharacteristic(final BluetoothGattCharacteristic characteristic) {
if (mBluetoothGatt == null) {
if (DEBUG_LOG) {
Log.w(TAG, "mBluetoothGatt not initialized");
}
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
/**
* writeTXCharacteristic.
* @param value value
*/
public void writeTXCharacteristic(final byte[] value) {
BluetoothGattCharacteristic rxChar;
BluetoothGattService rxService = mBluetoothGatt.getService(RX_SERVICE_UUID2);
if (rxService == null) {
showMessage("mBluetoothGatt null" + mBluetoothGatt);
showMessage("Tx service not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
rxChar = rxService.getCharacteristic(RX_CHAR_UUID2);
if (rxChar == null) {
showMessage("Tx charateristic not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
if (DEBUG_LOG) {
Log.w(TAG, "TxChar = " + RX_CHAR_UUID2.toString());
}
rxChar.setValue(value);
boolean status = mBluetoothGatt.writeCharacteristic(rxChar);
if (DEBUG_LOG) {
Log.d(TAG, "write TXchar - status=" + status);
}
}
/**
* readNameCharacteristic.
*/
public void readNameCharacteristic() {
BluetoothGattService rxService = mBluetoothGatt.getService(RX_SERVICE_UUID2);
if (rxService == null) {
showMessage("Rx service not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
BluetoothGattCharacteristic nameChar = rxService.getCharacteristic(NAME_CHAR_UUID);
if (nameChar == null) {
showMessage("Name charateristic not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
boolean status = mBluetoothGatt.readCharacteristic(nameChar);
if (DEBUG_LOG) {
Log.d(TAG, "read NAMEchar - status=" + status);
}
}
/**
* writeNameCharacteristic.
* @param value value
*/
public void writeNameCharacteristic(final byte[] value) {
BluetoothGattService rxService = mBluetoothGatt.getService(RX_SERVICE_UUID2);
if (rxService == null) {
showMessage("Rx service not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
BluetoothGattCharacteristic nameChar = rxService.getCharacteristic(NAME_CHAR_UUID);
if (nameChar == null) {
showMessage("Rx charateristic not found!");
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
nameChar.setValue(value);
boolean status = mBluetoothGatt.writeCharacteristic(nameChar);
if (DEBUG_LOG) {
Log.d(TAG, "write NAMEchar - status=" + status);
}
}
/**
* showMessage.
* @param msg msg
*/
private void showMessage(final String msg) {
if (DEBUG_LOG) {
Log.e(TAG, msg);
}
}
/**
* getmConnectionState.
* @return ConnectionState
*/
public int getmConnectionState() {
return mConnectionState;
}
/**
* setmConnectionState.
* @param connectionState connectionState
*/
public void setmConnectionState(final int connectionState) {
this.mConnectionState = connectionState;
}
}