package com.pixplicity.bluetoothdemo; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter.LeScanCallback; 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; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.util.UUID; /** * Main screen of the application. This is where all the magic happens! * Usually, to keep your code clean you'd want to move all the logic concerning bluetooth to a * separate controller, a {@link Service service}, a utility class or a combination of those. */ public class MainActivity extends AppCompatActivity implements LeScanCallback { /** * The GATT standard defines this UUID as the identifier of the update notification descriptor, * i.e. the descriptor of a characteristic that defines if you will receive updates on the * value of the characteristic. */ private static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); // TODO Change this value to the UUID of the service you want to communicate with private static final UUID SERVICE_UUID = UUID.fromString("e0a9b597-68f7-4f45-91b1-8de008987048"); // TODO change this value to the UUID of the characteristic you want to write to private static final UUID CHARACTERISTIC_A_WRITE = UUID.fromString("186616c7-2993-4ef5-b2a9-04fae246cbb6"); // TODO change this value to the UUID of the characteristic you want to receive updates from private static final UUID CHARACTERISTIC_B_READ = UUID.fromString("f14b30fd-d8ad-4ed4-aac0-6d27465b9601"); /** * Request code used when starting the bluetooth settings Activity */ private static final int REQUEST_ENABLE_BT = RESULT_FIRST_USER; private static final String TAG = MainActivity.class.getSimpleName(); // UI elements private Button mBtScan, mBtWrite; private EditText mEtUUID; private View mScanResults; private TextView mTvScanResults, mTvStatus, mTvCharacteristic; private ProgressBar mProgress; // Bluetooth objects private BluetoothAdapter mAdapter; private BluetoothGatt mGatt; private BluetoothGattCharacteristic mWriteCharacteristic; private boolean mIsScanning = false; private boolean mSearchIsUUID; private byte mWriteValue = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initialize views mBtScan = (Button) findViewById(R.id.bt_scan); mBtWrite = (Button) findViewById(R.id.bt_write); mEtUUID = (EditText) findViewById(R.id.et_uuid); mProgress = (ProgressBar) findViewById(R.id.busy); mScanResults = findViewById(R.id.scan_results); mTvScanResults = (TextView) findViewById(R.id.tv_scan_results); mTvStatus = (TextView) findViewById(R.id.tv_status); mTvCharacteristic = (TextView) findViewById(R.id.tv_characteristic); // Button listeners mBtScan.setOnClickListener(new OnClickListener() { @Override public void onClick(@NonNull View view) { startScan(); } }); findViewById(R.id.bt_clear).setOnClickListener(new OnClickListener() { @Override public void onClick(@NonNull View view) { mEtUUID.setText(""); } }); mBtWrite.setText(getString(R.string.bt_write, mWriteValue)); mBtWrite.setOnClickListener(new OnClickListener() { @Override public void onClick(@NonNull View v) { write(); } }); // Initialize the adapter BluetoothManager btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mAdapter = btManager.getAdapter(); if (!isBluetoothEnabled()) { // If bluetooth is not enabled, we ask the user to do so Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, REQUEST_ENABLE_BT); // If the permission android.permission.BLUETOOTH_ADMIN is included in the manifest, // then we could also enable bluetooth without requiring a user action by doing: // mAdapter.enable(); // ...But that is not nice towards the user. } } /** * Updates the status TextView with the given string resource. * Can be called from a background thread. * * @param strId The resource id of the String to display. */ private void showStatus(final int strId) { runOnUiThread(new Runnable() { @Override public void run() { mTvStatus.setText(strId); } }); } @Override protected void onResume() { super.onResume(); // Enable the scan button if bluetooth is enabled and if // it is not already scanning mBtScan.setEnabled(isBluetoothEnabled() && !mIsScanning); } @Override protected void onPause() { super.onPause(); if (mIsScanning && mAdapter != null) { // Stop scanning! We're done! mAdapter.stopLeScan(this); scanStopped(); } if (mGatt != null) { // Clean up! This is important, as not closing the GATT connection can // cause problems when we want to reconnect later on. // Depending on the Android version there's also a very limited number of // simultaneous connections available (system wide), so we should free up connections // as soon as we can. mGatt.disconnect(); mGatt.close(); mGatt = null; } } /** * Checks if bluetooth is enabled in the system settings * * @return {@code true} if bluetooth is available and enabled, {@code false} otherwise */ public boolean isBluetoothEnabled() { return mAdapter != null && mAdapter.isEnabled(); } /** * Start scanning for nearby devices. If a UUID is entered in the EditText, it will scan * for that specific service. If a MAC address or nothing is entered it will scan for all * devices. * * @see #onLeScan(BluetoothDevice, int, byte[]) */ private void startScan() { mIsScanning = true; mProgress.setVisibility(View.VISIBLE); mBtScan.setEnabled(false); // If the input contains a colon, let's assume it is a MAC-address if (TextUtils.isEmpty(mEtUUID.getText()) || mEtUUID.getText().toString().contains(":")) { // Start a regular scan for all devices mSearchIsUUID = false; mAdapter.startLeScan(this); } else { String uuidStr = mEtUUID.getText().toString(); try { mSearchIsUUID = true; UUID uuid = UUID.fromString(uuidStr); // Start a scan for a service with a specific UUID mAdapter.startLeScan(new UUID[]{uuid}, this); } catch (IllegalArgumentException e) { // Abort scanning e.printStackTrace(); Toast.makeText(this, R.string.toast_invalid_uuid, Toast.LENGTH_LONG).show(); scanStopped(); } } } /** * Closes the GATT connection (if any) and updates the UI to show * the 'disconnected' state. Can be called from background threads. */ private void cleanUp() { showStatus(R.string.status_disconnected); runOnUiThread(new Runnable() { @Override public void run() { scanStopped(); mTvCharacteristic.setVisibility(View.GONE); } }); if (mGatt != null) { mGatt.disconnect(); mGatt.close(); mGatt = null; } } /** * Enables the UI elements to start a new scan. */ private void scanStopped() { mIsScanning = false; mProgress.setVisibility(View.INVISIBLE); mBtScan.setEnabled(true); } /** * Writes a value (alternating 0 and 1) to the characteristics */ private void write() { if (mWriteCharacteristic == null) { Log.e(TAG, "There's no characteristic to write to"); } // Disable until write has finished to prevent sending faster than the connection can handle mBtWrite.setEnabled(false); if ((mWriteCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { Log.i(TAG, "Writing data to bluetooth device..."); mWriteCharacteristic.setValue(new byte[]{mWriteValue}); mGatt.writeCharacteristic(mWriteCharacteristic); } else { Log.w(TAG, "Characteristic " + CHARACTERISTIC_A_WRITE + " not writable"); } } /** * Called for every device that is found during the scan * * @param device The device that is found * @param rssi The received signal strength indication * @param scanRecord Extra data concerning the scanned device */ @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { mScanResults.setVisibility(View.VISIBLE); mTvScanResults.append(getString(R.string.found_device, device.getAddress())); String search = mEtUUID.getText().toString(); if (mSearchIsUUID) { // When scanning for a service uuid, we can safely assume that the found devices // have the service we're connectDevice(device); } else if (search.contains(":") && device.getAddress().equals(search)) { // Does the device address match our search term? connectDevice(device); } } /** * Starts connecting to a device. The device is obtained a scan. * * @param device The device to connect with */ private void connectDevice(final BluetoothDevice device) { mTvStatus.setVisibility(View.VISIBLE); mTvStatus.setText(R.string.status_connecting); // We've found the device we want, so stop scanning. // This is important, because scanning is the most battery intensive part of the process. mAdapter.stopLeScan(this); scanStopped(); // Connect to the device mGatt = device.connectGatt(this, false, new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (status == BluetoothGatt.GATT_SUCCESS) { switch (newState) { case BluetoothProfile.STATE_CONNECTED: showStatus(R.string.status_discovering); // Start discovering services. // Once done, the onServicesDiscovered callback will be called. if (!gatt.discoverServices()) { // If it fails, clean up cleanUp(); } break; case BluetoothProfile.STATE_DISCONNECTED: // The connection was closed, update the interface: showStatus(R.string.status_disconnected); break; } } else { // Connection failed, clean up cleanUp(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { // Find the service we want to use BluetoothGattService service = gatt.getService(SERVICE_UUID); // Find the characteristic that we want to write to mWriteCharacteristic = service.getCharacteristic(CHARACTERISTIC_A_WRITE); // Find the characteristic that we want to read from BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_B_READ); // Enable notifications of that characteristic if (!gatt.setCharacteristicNotification(characteristic, true)) { Log.w(TAG, "Unable to get notifications for characteristic " + CHARACTERISTIC_B_READ); return; } // Enable notifications even further by enabling it in the characteristic's descriptors BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); if (!gatt.writeDescriptor(descriptor)) { Log.w(TAG, "Unable to write to descriptor of characteristic " + characteristic.getUuid()); cleanUp(); } else { showStatus(R.string.status_connected); runOnUiThread(new Runnable() { @Override public void run() { findViewById(R.id.read_write).setVisibility(View.VISIBLE); } }); } } /** * Once notifications are enabled, this method is called every time the value of the characteristic changes. * * @param gatt The GATT connection * @param characteristic The characteristic that has changed. */ @Override public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { Log.i(TAG, "Characteristic changed: " + characteristic.getUuid() + " = " + characteristic.getValue()[0]); // Updating the interface needs to be executed on the UI thread runOnUiThread(new Runnable() { @Override public void run() { // The value can be of a variety of types. In this example we check for a // single byte; your actual device might give a String or integer instead byte value = characteristic.getValue()[0]; mTvCharacteristic.setVisibility(View.VISIBLE); mTvCharacteristic.setText(String.valueOf(value)); } }); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status != BluetoothGatt.GATT_SUCCESS) { Log.w(TAG, "Unable to write to characteristic " + characteristic.getUuid()); cleanUp(); } else { // Update interface runOnUiThread(new Runnable() { @Override public void run() { mBtWrite.setEnabled(true); mWriteValue = (byte) ((mWriteValue + 1) % 2); mBtWrite.setText(getString(R.string.bt_write, mWriteValue)); } }); } } }); } }