From 131ab06bfb0f8da251d3cb508b797319d753b291 Mon Sep 17 00:00:00 2001 From: Alexandros Schillings Date: Mon, 28 Apr 2014 16:42:12 +0100 Subject: [PATCH] Started rewriting the Gatt Screen UI --- .../res/layout/activity_gatt_services.xml | 126 +++-- .../res/layout/list_item_view_adrecord.xml | 4 +- sample_app/res/values/strings.xml | 3 + .../activities/DeviceControlActivity.java | 494 +++++++++--------- .../btlescan/services/BluetoothLeService.java | 459 ++++++++-------- 5 files changed, 550 insertions(+), 536 deletions(-) diff --git a/sample_app/res/layout/activity_gatt_services.xml b/sample_app/res/layout/activity_gatt_services.xml index 9fdc916..b60e56f 100644 --- a/sample_app/res/layout/activity_gatt_services.xml +++ b/sample_app/res/layout/activity_gatt_services.xml @@ -14,86 +14,112 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" > - + android:layout_alignParentTop="true" + android:columnCount="2" + android:useDefaultMargins="true" > - - + android:text="@string/label_device_address" /> - - - + style="@style/GridLayoutDataTextView" /> - - + android:text="@string/label_state" /> - + style="@style/GridLayoutDataTextView" /> + - + + + android:layout_alignParentBottom="true" + android:columnCount="2" + android:useDefaultMargins="true" > - - + android:text="@string/label_uuid" /> + + - + android:text="@string/label_desc" /> + + + + + + + + + + + + + + android:layout_height="fill_parent" + android:layout_above="@id/lowerSepparator" + android:layout_below="@id/upperSepparator" /> - \ No newline at end of file + \ No newline at end of file diff --git a/sample_app/res/layout/list_item_view_adrecord.xml b/sample_app/res/layout/list_item_view_adrecord.xml index ed0adc6..e5cd158 100644 --- a/sample_app/res/layout/list_item_view_adrecord.xml +++ b/sample_app/res/layout/list_item_view_adrecord.xml @@ -23,7 +23,7 @@ style="@style/GridLayoutTitleTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="As String:" /> + android:text="@string/label_as_string" /> + android:text="@string/label_as_array" /> Device Info Invalid Device Data! unknown + As String: + As Array: + Desc: \ No newline at end of file diff --git a/sample_app/src/uk/co/alt236/btlescan/activities/DeviceControlActivity.java b/sample_app/src/uk/co/alt236/btlescan/activities/DeviceControlActivity.java index d18d16b..a3ce5e6 100644 --- a/sample_app/src/uk/co/alt236/btlescan/activities/DeviceControlActivity.java +++ b/sample_app/src/uk/co/alt236/btlescan/activities/DeviceControlActivity.java @@ -19,8 +19,10 @@ package uk.co.alt236.btlescan.activities; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver; +import uk.co.alt236.bluetoothlelib.util.ByteUtils; import uk.co.alt236.btlescan.R; import uk.co.alt236.btlescan.services.BluetoothLeService; import android.app.Activity; @@ -41,6 +43,8 @@ import android.view.View; import android.widget.ExpandableListView; import android.widget.SimpleExpandableListAdapter; import android.widget.TextView; +import butterknife.ButterKnife; +import butterknife.InjectView; /** * For a given BLE device, this Activity provides the user interface to connect, display data, @@ -49,264 +53,286 @@ import android.widget.TextView; * Bluetooth LE API. */ public class DeviceControlActivity extends Activity { - private final static String TAG = DeviceControlActivity.class.getSimpleName(); + private final static String TAG = DeviceControlActivity.class.getSimpleName(); - public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME"; - public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS"; + public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME"; + public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS"; - private TextView mConnectionState; - private TextView mDataField; - private String mDeviceName; - private String mDeviceAddress; - private ExpandableListView mGattServicesList; - private BluetoothLeService mBluetoothLeService; - private ArrayList> mGattCharacteristics = - new ArrayList>(); - private boolean mConnected = false; - private BluetoothGattCharacteristic mNotifyCharacteristic; + private static final String LIST_NAME = "NAME"; + private static final String LIST_UUID = "UUID"; - private final String LIST_NAME = "NAME"; - private final String LIST_UUID = "UUID"; + private BluetoothGattCharacteristic mNotifyCharacteristic; + private BluetoothLeService mBluetoothLeService; + private List> mGattCharacteristics = new ArrayList>(); + private String mDeviceAddress; + private String mDeviceName; - // Code to manage Service lifecycle. - private final ServiceConnection mServiceConnection = new ServiceConnection() { + @InjectView(R.id.gatt_services_list) ExpandableListView mGattServicesList; + @InjectView(R.id.connection_state) TextView mConnectionState; - @Override - public void onServiceConnected(ComponentName componentName, IBinder service) { - mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); - if (!mBluetoothLeService.initialize()) { - Log.e(TAG, "Unable to initialize Bluetooth"); - finish(); - } - // Automatically connects to the device upon successful start-up initialization. - mBluetoothLeService.connect(mDeviceAddress); - } + @InjectView(R.id.uuid) TextView mGattUUID; + @InjectView(R.id.description) TextView mGattUUIDDesc; + @InjectView(R.id.data_as_string) TextView mDataAsString; + @InjectView(R.id.data_as_array) TextView mDataAsArray; - @Override - public void onServiceDisconnected(ComponentName componentName) { - mBluetoothLeService = null; - } - }; + private boolean mConnected = false; - // Handles various events fired by the Service. - // ACTION_GATT_CONNECTED: connected to a GATT server. - // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. - // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. - // ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read - // or notification operations. - private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { - mConnected = true; - updateConnectionState(R.string.connected); - invalidateOptionsMenu(); - } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { - mConnected = false; - updateConnectionState(R.string.disconnected); - invalidateOptionsMenu(); - clearUI(); - } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { - // Show all the supported services and characteristics on the user interface. - displayGattServices(mBluetoothLeService.getSupportedGattServices()); - } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { - displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); - } - } - }; + // Code to manage Service lifecycle. + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder service) { + mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); + if (!mBluetoothLeService.initialize()) { + Log.e(TAG, "Unable to initialize Bluetooth"); + finish(); + } + // Automatically connects to the device upon successful start-up initialization. + mBluetoothLeService.connect(mDeviceAddress); + } - // If a given GATT characteristic is selected, check for supported features. This sample - // demonstrates 'Read' and 'Notify' features. See - // http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete - // list of supported characteristic features. - private final ExpandableListView.OnChildClickListener servicesListClickListner = - new ExpandableListView.OnChildClickListener() { - @Override - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, - int childPosition, long id) { - if (mGattCharacteristics != null) { - final BluetoothGattCharacteristic characteristic = - mGattCharacteristics.get(groupPosition).get(childPosition); - final int charaProp = characteristic.getProperties(); - if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) { - // If there is an active notification on a characteristic, clear - // it first so it doesn't update the data field on the user interface. - if (mNotifyCharacteristic != null) { - mBluetoothLeService.setCharacteristicNotification( - mNotifyCharacteristic, false); - mNotifyCharacteristic = null; - } - mBluetoothLeService.readCharacteristic(characteristic); - } - if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { - mNotifyCharacteristic = characteristic; - mBluetoothLeService.setCharacteristicNotification( - characteristic, true); - } - return true; - } - return false; - } - }; + @Override + public void onServiceDisconnected(ComponentName componentName) { + mBluetoothLeService = null; + } + }; - private void clearUI() { - mGattServicesList.setAdapter((SimpleExpandableListAdapter) null); - mDataField.setText(R.string.no_data); - } + // Handles various events fired by the Service. + // ACTION_GATT_CONNECTED: connected to a GATT server. + // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. + // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. + // ACTION_DATA_AVAILABLE: received data from the device. + // this can be a result of read or notification operations. + private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { + mConnected = true; + updateConnectionState(R.string.connected); + invalidateOptionsMenu(); + } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { + mConnected = false; + updateConnectionState(R.string.disconnected); + invalidateOptionsMenu(); + clearUI(); + } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { + // Show all the supported services and characteristics on the user interface. + displayGattServices(mBluetoothLeService.getSupportedGattServices()); + } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { + final String noData = getString(R.string.no_data); + final String uuid = intent.getStringExtra(BluetoothLeService.EXTRA_UUID_CHAR); + final byte[] dataArr = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA_RAW); - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_gatt_services); + mGattUUID.setText(tryString(uuid, noData)); + mGattUUIDDesc.setText(GattAttributeResolver.getAttributeName(uuid, getString(R.string.unknown))); + mDataAsArray.setText(ByteUtils.byteArrayToHexString(dataArr)); + mDataAsString.setText(new String(dataArr)); + } + } + }; - final Intent intent = getIntent(); - mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME); - mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS); + // If a given GATT characteristic is selected, check for supported features. This sample + // demonstrates 'Read' and 'Notify' features. See + // http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete + // list of supported characteristic features. + private final ExpandableListView.OnChildClickListener servicesListClickListner = new ExpandableListView.OnChildClickListener() { + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { + if (mGattCharacteristics != null) { + final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(groupPosition).get(childPosition); + final int charaProp = characteristic.getProperties(); + if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) { + // If there is an active notification on a characteristic, clear + // it first so it doesn't update the data field on the user interface. + if (mNotifyCharacteristic != null) { + mBluetoothLeService.setCharacteristicNotification(mNotifyCharacteristic, false); + mNotifyCharacteristic = null; + } + mBluetoothLeService.readCharacteristic(characteristic); + } + if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + mNotifyCharacteristic = characteristic; + mBluetoothLeService.setCharacteristicNotification(characteristic, true); + } + return true; + } + return false; + } + }; - // Sets up UI references. - ((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress); - mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list); - mGattServicesList.setOnChildClickListener(servicesListClickListner); - mConnectionState = (TextView) findViewById(R.id.connection_state); - mDataField = (TextView) findViewById(R.id.data_value); - getActionBar().setTitle(mDeviceName); - getActionBar().setDisplayHomeAsUpEnabled(true); - Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); - bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE); - } + private void clearUI() { + mGattServicesList.setAdapter((SimpleExpandableListAdapter) null); + mGattUUID.setText(R.string.no_data); + mGattUUIDDesc.setText(R.string.no_data); + mDataAsArray.setText(R.string.no_data); + mDataAsString.setText(R.string.no_data); - @Override - protected void onResume() { - super.onResume(); - registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); - if (mBluetoothLeService != null) { - final boolean result = mBluetoothLeService.connect(mDeviceAddress); - Log.d(TAG, "Connect request result=" + result); - } - } + } - @Override - protected void onPause() { - super.onPause(); - unregisterReceiver(mGattUpdateReceiver); - } + // Demonstrates how to iterate through the supported GATT Services/Characteristics. + // In this sample, we populate the data structure that is bound to the ExpandableListView + // on the UI. + private void displayGattServices(List gattServices) { + if (gattServices == null) return; + String uuid = null; + final String unknownServiceString = getResources().getString(R.string.unknown_service); + final String unknownCharaString = getResources().getString(R.string.unknown_characteristic); + final List> gattServiceData = new ArrayList>(); + final List>> gattCharacteristicData = new ArrayList>>(); + mGattCharacteristics = new ArrayList>(); - @Override - protected void onDestroy() { - super.onDestroy(); - unbindService(mServiceConnection); - mBluetoothLeService = null; - } + // Loops through available GATT Services. + for (BluetoothGattService gattService : gattServices) { + final Map currentServiceData = new HashMap(); + uuid = gattService.getUuid().toString(); + currentServiceData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownServiceString)); + currentServiceData.put(LIST_UUID, uuid); + gattServiceData.add(currentServiceData); - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.gatt_services, menu); - if (mConnected) { - menu.findItem(R.id.menu_connect).setVisible(false); - menu.findItem(R.id.menu_disconnect).setVisible(true); - } else { - menu.findItem(R.id.menu_connect).setVisible(true); - menu.findItem(R.id.menu_disconnect).setVisible(false); - } - return true; - } + final List> gattCharacteristicGroupData = new ArrayList>(); + final List gattCharacteristics = gattService.getCharacteristics(); + final List charas = new ArrayList(); - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()) { - case R.id.menu_connect: - mBluetoothLeService.connect(mDeviceAddress); - return true; - case R.id.menu_disconnect: - mBluetoothLeService.disconnect(); - return true; - case android.R.id.home: - onBackPressed(); - return true; - } - return super.onOptionsItemSelected(item); - } + // Loops through available Characteristics. + for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { + charas.add(gattCharacteristic); + final Map currentCharaData = new HashMap(); + uuid = gattCharacteristic.getUuid().toString(); + currentCharaData.put(LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString)); + currentCharaData.put(LIST_UUID, uuid); + gattCharacteristicGroupData.add(currentCharaData); + } + mGattCharacteristics.add(charas); + gattCharacteristicData.add(gattCharacteristicGroupData); + } - private void updateConnectionState(final int resourceId) { - runOnUiThread(new Runnable() { - @Override - public void run() { - mConnectionState.setText(resourceId); - } - }); - } + final SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter( + this, + gattServiceData, + android.R.layout.simple_expandable_list_item_2, + new String[] {LIST_NAME, LIST_UUID}, + new int[] { android.R.id.text1, android.R.id.text2 }, + gattCharacteristicData, + android.R.layout.simple_expandable_list_item_2, + new String[] {LIST_NAME, LIST_UUID}, + new int[] { android.R.id.text1, android.R.id.text2 } + ); + mGattServicesList.setAdapter(gattServiceAdapter); + } - private void displayData(String data) { - if (data != null) { - mDataField.setText(data); - } - } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_gatt_services); - // Demonstrates how to iterate through the supported GATT Services/Characteristics. - // In this sample, we populate the data structure that is bound to the ExpandableListView - // on the UI. - private void displayGattServices(List gattServices) { - if (gattServices == null) return; - String uuid = null; - String unknownServiceString = getResources().getString(R.string.unknown_service); - String unknownCharaString = getResources().getString(R.string.unknown_characteristic); - ArrayList> gattServiceData = new ArrayList>(); - ArrayList>> gattCharacteristicData - = new ArrayList>>(); - mGattCharacteristics = new ArrayList>(); + final Intent intent = getIntent(); + mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME); + mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS); - // Loops through available GATT Services. - for (BluetoothGattService gattService : gattServices) { - HashMap currentServiceData = new HashMap(); - uuid = gattService.getUuid().toString(); - currentServiceData.put( - LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownServiceString)); - currentServiceData.put(LIST_UUID, uuid); - gattServiceData.add(currentServiceData); + ButterKnife.inject(this); - ArrayList> gattCharacteristicGroupData = - new ArrayList>(); - List gattCharacteristics = - gattService.getCharacteristics(); - ArrayList charas = - new ArrayList(); + // Sets up UI references. + ((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress); + mGattServicesList.setOnChildClickListener(servicesListClickListner); - // Loops through available Characteristics. - for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { - charas.add(gattCharacteristic); - HashMap currentCharaData = new HashMap(); - uuid = gattCharacteristic.getUuid().toString(); - currentCharaData.put( - LIST_NAME, GattAttributeResolver.getAttributeName(uuid, unknownCharaString)); - currentCharaData.put(LIST_UUID, uuid); - gattCharacteristicGroupData.add(currentCharaData); - } - mGattCharacteristics.add(charas); - gattCharacteristicData.add(gattCharacteristicGroupData); - } + getActionBar().setTitle(mDeviceName); + getActionBar().setDisplayHomeAsUpEnabled(true); - SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter( - this, - gattServiceData, - android.R.layout.simple_expandable_list_item_2, - new String[] {LIST_NAME, LIST_UUID}, - new int[] { android.R.id.text1, android.R.id.text2 }, - gattCharacteristicData, - android.R.layout.simple_expandable_list_item_2, - new String[] {LIST_NAME, LIST_UUID}, - new int[] { android.R.id.text1, android.R.id.text2 } - ); - mGattServicesList.setAdapter(gattServiceAdapter); - } + final Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); + bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE); + } - private static IntentFilter makeGattUpdateIntentFilter() { - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); - intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); - intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); - intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); - return intentFilter; - } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.gatt_services, menu); + if (mConnected) { + menu.findItem(R.id.menu_connect).setVisible(false); + menu.findItem(R.id.menu_disconnect).setVisible(true); + } else { + menu.findItem(R.id.menu_connect).setVisible(true); + menu.findItem(R.id.menu_disconnect).setVisible(false); + } + return true; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unbindService(mServiceConnection); + mBluetoothLeService = null; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_connect: + mBluetoothLeService.connect(mDeviceAddress); + return true; + case R.id.menu_disconnect: + mBluetoothLeService.disconnect(); + return true; + case android.R.id.home: + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(mGattUpdateReceiver); + } + + @Override + protected void onResume() { + super.onResume(); + registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); + if (mBluetoothLeService != null) { + final boolean result = mBluetoothLeService.connect(mDeviceAddress); + Log.d(TAG, "Connect request result=" + result); + } + } + + private void updateConnectionState(final int resourceId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + final int colourId; + + switch(resourceId){ + case R.string.connected: + colourId = android.R.color.holo_green_dark; + break; + case R.string.disconnected: + colourId = android.R.color.holo_red_dark; + break; + default: + colourId = android.R.color.black; + break; + } + + mConnectionState.setText(resourceId); + mConnectionState.setTextColor(getResources().getColor(colourId)); + } + }); + } + + private static IntentFilter makeGattUpdateIntentFilter() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); + intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); + return intentFilter; + } + + private static String tryString(String string, String fallback){ + if(string == null){ + return fallback; + } else{ + return string; + } + } } \ No newline at end of file diff --git a/sample_app/src/uk/co/alt236/btlescan/services/BluetoothLeService.java b/sample_app/src/uk/co/alt236/btlescan/services/BluetoothLeService.java index d5fb0b6..03f7539 100644 --- a/sample_app/src/uk/co/alt236/btlescan/services/BluetoothLeService.java +++ b/sample_app/src/uk/co/alt236/btlescan/services/BluetoothLeService.java @@ -17,16 +17,13 @@ package uk.co.alt236.btlescan.services; import java.util.List; -import java.util.UUID; -import uk.co.alt236.bluetoothlelib.resolvers.GattAttributeResolver; import android.app.Service; import android.bluetooth.BluetoothAdapter; 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.BluetoothManager; import android.bluetooth.BluetoothProfile; @@ -41,280 +38,242 @@ import android.util.Log; * given Bluetooth LE device. */ public class BluetoothLeService extends Service { - private final static String TAG = BluetoothLeService.class.getSimpleName(); + private final static String TAG = BluetoothLeService.class.getSimpleName(); - private BluetoothManager mBluetoothManager; - private BluetoothAdapter mBluetoothAdapter; - private String mBluetoothDeviceAddress; - private BluetoothGatt mBluetoothGatt; - private int mConnectionState = STATE_DISCONNECTED; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + private String mBluetoothDeviceAddress; + private BluetoothGatt mBluetoothGatt; + private int mConnectionState = STATE_DISCONNECTED; - private static final int STATE_DISCONNECTED = 0; - private static final int STATE_CONNECTING = 1; - private static final int STATE_CONNECTED = 2; + private static final int STATE_DISCONNECTED = 0; + private static final int STATE_CONNECTING = 1; + private static final int STATE_CONNECTED = 2; - public final static String ACTION_GATT_CONNECTED = - "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; - public final static String ACTION_GATT_DISCONNECTED = - "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; - public final static String ACTION_GATT_SERVICES_DISCOVERED = - "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; - public final static String ACTION_DATA_AVAILABLE = - "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; - public final static String EXTRA_DATA = - "com.example.bluetooth.le.EXTRA_DATA"; + public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; + public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; + public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; + public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; + public final static String EXTRA_DATA_RAW = "com.example.bluetooth.le.EXTRA_DATA_RAW"; + public final static String EXTRA_UUID_CHAR = "com.example.bluetooth.le.EXTRA_UUID_CHAR"; - public final static UUID UUID_HEART_RATE_MEASUREMENT = - UUID.fromString(GattAttributeResolver.HEART_RATE_MEASUREMENT); + // 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(BluetoothGatt gatt, int status, int newState) { + String intentAction; + if (newState == BluetoothProfile.STATE_CONNECTED) { + intentAction = ACTION_GATT_CONNECTED; + mConnectionState = STATE_CONNECTED; + broadcastUpdate(intentAction); + Log.i(TAG, "Connected to GATT server."); + // Attempts to discover services after successful connection. + Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); - // 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(BluetoothGatt gatt, int status, int newState) { - String intentAction; - if (newState == BluetoothProfile.STATE_CONNECTED) { - intentAction = ACTION_GATT_CONNECTED; - mConnectionState = STATE_CONNECTED; - broadcastUpdate(intentAction); - Log.i(TAG, "Connected to GATT server."); - // Attempts to discover services after successful connection. - Log.i(TAG, "Attempting to start service discovery:" + - mBluetoothGatt.discoverServices()); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + intentAction = ACTION_GATT_DISCONNECTED; + mConnectionState = STATE_DISCONNECTED; + Log.i(TAG, "Disconnected from GATT server."); + broadcastUpdate(intentAction); + } + } - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - intentAction = ACTION_GATT_DISCONNECTED; - mConnectionState = STATE_DISCONNECTED; - Log.i(TAG, "Disconnected from GATT server."); - broadcastUpdate(intentAction); - } - } + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); + } else { + Log.w(TAG, "onServicesDiscovered received: " + status); + } + } - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); - } else { - Log.w(TAG, "onServicesDiscovered received: " + status); - } - } + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); + } + } - @Override - public void onCharacteristicRead(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, - int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); - } - } + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); + } + }; - @Override - public void onCharacteristicChanged(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic) { - broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); - } - }; + private void broadcastUpdate(final String action) { + final Intent intent = new Intent(action); + sendBroadcast(intent); + } - private void broadcastUpdate(final String action) { - final Intent intent = new Intent(action); - sendBroadcast(intent); - } + private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { + final Intent intent = new Intent(action); + intent.putExtra(EXTRA_UUID_CHAR, characteristic.getUuid().toString()); - private void broadcastUpdate(final String action, - final BluetoothGattCharacteristic characteristic) { - final Intent intent = new Intent(action); + // Always try to add the RAW value + final byte[] data = characteristic.getValue(); + if (data != null && data.length > 0) { + intent.putExtra(EXTRA_DATA_RAW, data); + } - // 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 (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { - int flag = characteristic.getProperties(); - int format = -1; - if ((flag & 0x01) != 0) { - format = BluetoothGattCharacteristic.FORMAT_UINT16; - Log.d(TAG, "Heart rate format UINT16."); - } else { - format = BluetoothGattCharacteristic.FORMAT_UINT8; - Log.d(TAG, "Heart rate format UINT8."); - } - final int heartRate = characteristic.getIntValue(format, 1); - Log.d(TAG, String.format("Received heart rate: %d", heartRate)); - intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); - } else { - // For all other profiles, writes the data formatted in HEX. - final byte[] data = characteristic.getValue(); - if (data != null && data.length > 0) { - final StringBuilder stringBuilder = new StringBuilder(data.length); - for(byte byteChar : data) - stringBuilder.append(String.format("%02X ", byteChar)); - intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); - } - } - sendBroadcast(intent); - } + sendBroadcast(intent); + } - public class LocalBinder extends Binder { - public BluetoothLeService getService() { - return BluetoothLeService.this; - } - } + public class LocalBinder extends Binder { + public BluetoothLeService getService() { + return BluetoothLeService.this; + } + } - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } - @Override - public boolean onUnbind(Intent intent) { - // After using a given device, you should make sure that BluetoothGatt.close() is called - // such that resources are cleaned up properly. In this particular example, close() is - // invoked when the UI is disconnected from the Service. - close(); - return super.onUnbind(intent); - } + @Override + public boolean onUnbind(Intent intent) { + // After using a given device, you should make sure that BluetoothGatt.close() is called + // such that resources are cleaned up properly. In this particular example, close() is + // invoked when the UI is disconnected from the Service. + close(); + return super.onUnbind(intent); + } - private final IBinder mBinder = new LocalBinder(); + private final IBinder mBinder = new LocalBinder(); - /** - * Initializes a reference to the local Bluetooth adapter. - * - * @return Return true if the initialization is successful. - */ - public boolean initialize() { - // For API level 18 and above, get a reference to BluetoothAdapter through - // BluetoothManager. - if (mBluetoothManager == null) { - mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); - if (mBluetoothManager == null) { - Log.e(TAG, "Unable to initialize BluetoothManager."); - return false; - } - } + /** + * Initializes a reference to the local Bluetooth adapter. + * + * @return Return true if the initialization is successful. + */ + public boolean initialize() { + // For API level 18 and above, get a reference to BluetoothAdapter through + // BluetoothManager. + if (mBluetoothManager == null) { + mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + Log.e(TAG, "Unable to initialize BluetoothManager."); + return false; + } + } - mBluetoothAdapter = mBluetoothManager.getAdapter(); - if (mBluetoothAdapter == null) { - Log.e(TAG, "Unable to obtain a BluetoothAdapter."); - return false; - } + mBluetoothAdapter = mBluetoothManager.getAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "Unable to obtain a BluetoothAdapter."); + return false; + } - return true; - } + return true; + } - /** - * Connects to the GATT server hosted on the Bluetooth LE device. - * - * @param address 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 String address) { - if (mBluetoothAdapter == null || address == null) { - Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); - return false; - } + /** + * Connects to the GATT server hosted on the Bluetooth LE device. + * + * @param address 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 String address) { + if (mBluetoothAdapter == null || address == null) { + Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); + return false; + } - // Previously connected device. Try to reconnect. - if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) - && mBluetoothGatt != null) { - Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); - if (mBluetoothGatt.connect()) { - mConnectionState = STATE_CONNECTING; - return true; - } else { - return false; - } - } + // Previously connected device. Try to reconnect. + if (mBluetoothDeviceAddress != null + && address.equals(mBluetoothDeviceAddress) + && mBluetoothGatt != null) { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); - if (device == null) { - Log.w(TAG, "Device not found. Unable to connect."); - return false; - } - // We want to directly connect to the device, so we are setting the autoConnect - // parameter to false. - mBluetoothGatt = device.connectGatt(this, false, mGattCallback); - Log.d(TAG, "Trying to create a new connection."); - mBluetoothDeviceAddress = address; - mConnectionState = STATE_CONNECTING; - return true; - } + Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); + if (mBluetoothGatt.connect()) { + mConnectionState = STATE_CONNECTING; + return true; + } else { + return false; + } + } - /** - * 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 (mBluetoothAdapter == null || mBluetoothGatt == null) { - Log.w(TAG, "BluetoothAdapter not initialized"); - return; - } - mBluetoothGatt.disconnect(); - } + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); + if (device == null) { + Log.w(TAG, "Device not found. Unable to connect."); + return false; + } + // We want to directly connect to the device, so we are setting the autoConnect + // parameter to false. + mBluetoothGatt = device.connectGatt(this, false, mGattCallback); + Log.d(TAG, "Trying to create a new connection."); + mBluetoothDeviceAddress = address; + mConnectionState = STATE_CONNECTING; + return true; + } - /** - * 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; - } - mBluetoothGatt.close(); - mBluetoothGatt = null; - } + /** + * 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 (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.disconnect(); + } - /** - * 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(BluetoothGattCharacteristic characteristic) { - if (mBluetoothAdapter == null || mBluetoothGatt == null) { - Log.w(TAG, "BluetoothAdapter not initialized"); - return; - } - mBluetoothGatt.readCharacteristic(characteristic); - } + /** + * 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; + } + mBluetoothGatt.close(); + mBluetoothGatt = null; + } - /** - * Enables or disables notification on a give characteristic. - * - * @param characteristic Characteristic to act on. - * @param enabled If true, enable notification. False otherwise. - */ - public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, - boolean enabled) { - if (mBluetoothAdapter == null || mBluetoothGatt == null) { - Log.w(TAG, "BluetoothAdapter not initialized"); - return; - } - mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); + /** + * 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(BluetoothGattCharacteristic characteristic) { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.readCharacteristic(characteristic); + } - // This is specific to Heart Rate Measurement. - if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { - BluetoothGattDescriptor descriptor = characteristic.getDescriptor( - UUID.fromString(GattAttributeResolver.CLIENT_CHARACTERISTIC_CONFIG)); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - mBluetoothGatt.writeDescriptor(descriptor); - } - } + /** + * Enables or disables notification on a give characteristic. + * + * @param characteristic Characteristic to act on. + * @param enabled If true, enable notification. False otherwise. + */ + public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); + } - /** - * 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 null; + /** + * 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 null; - return mBluetoothGatt.getServices(); - } + return mBluetoothGatt.getServices(); + } } \ No newline at end of file