blob: 800ba35fa5b39b230fe4260760db540f17097868 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.android.impl.google.discovery.plugins.ble;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import com.google.common.collect.Queues;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* A reader that reads Vanadium Gatt services from remote Gatt servers.
*/
class GattReader extends BluetoothGattCallback {
private static final String TAG = Driver.TAG;
// A handler that will get called when a GATT service is read.
interface Handler {
void onServiceRead(BluetoothDevice device, BluetoothGattService service);
void onServiceReadFailed(BluetoothDevice device, UUID uuid);
}
// TODO(jhahn): What's the maximum MTU size in Android?
// Android seems to support up to 517 bytes. But there is no documentation on it.
private static final int MTU = 512;
// We serialize all Gatt requests. We cancel the request if it takes too
// long or hangs in order to prevent it from blocking other tasks.
//
// TODO(jhahn): Revisit the timeout.
private static final long GATT_TIMEOUT_MS = 10000; // 10 seconds.
private final Context mContext;
private final ExecutorService mExecutor;
private final Timer mTimer;
private final Handler mHandler;
private final ArrayDeque<Pair<BluetoothDevice, UUID>> mPendingReads;
private Pair<BluetoothDevice, UUID> mCurrentRead;
private BluetoothGatt mCurrentGatt;
private TimerTask mCurrentGattTimeout;
private BluetoothGattService mCurrentService;
private Iterator<BluetoothGattCharacteristic> mCurrentCharacteristicIterator;
GattReader(Context context, Handler handler) {
mContext = context;
mExecutor = Executors.newSingleThreadExecutor();
mTimer = new Timer();
mHandler = handler;
mPendingReads = Queues.newArrayDeque();
}
/**
* Reads a specified service from a remote device as well as their characteristics.
* <p/>
* This is an asynchronous operation. Once service read is completed, the onServiceRead() or
* onServiceReadFailed() callback is triggered.
*/
synchronized void readService(BluetoothDevice device, UUID uuid) {
mPendingReads.add(Pair.create(device, uuid));
if (mCurrentRead == null) {
maybeReadNextService();
}
}
/**
* Closes the Gatt reader cancelling the current read and deleting all pending requests.
*/
synchronized void close() {
if (mCurrentGatt != null) {
mCurrentGatt.disconnect();
}
mTimer.cancel();
mPendingReads.clear();
}
private synchronized void maybeReadNextService() {
mCurrentGatt = null;
mCurrentGattTimeout = null;
mCurrentService = null;
mCurrentCharacteristicIterator = null;
mCurrentRead = mPendingReads.poll();
if (mCurrentRead == null) {
return;
}
mCurrentGatt = mCurrentRead.first.connectGatt(mContext, false, this);
mCurrentGattTimeout =
new TimerTask() {
@Override
public void run() {
Log.e(TAG, "gatt operation timed out: " + mCurrentRead.first);
cancelAndMaybeReadNextService();
}
};
mTimer.schedule(mCurrentGattTimeout, GATT_TIMEOUT_MS);
}
private synchronized void finishAndMaybeReadNextService() {
mCurrentGattTimeout.cancel();
mCurrentGatt.disconnect();
final BluetoothDevice currentDevice = mCurrentRead.first;
final BluetoothGattService currentService = mCurrentService;
mExecutor.submit(
new Runnable() {
@Override
public void run() {
mHandler.onServiceRead(currentDevice, currentService);
}
});
maybeReadNextService();
}
private synchronized void cancelAndMaybeReadNextService() {
mCurrentGattTimeout.cancel();
mCurrentGatt.disconnect();
final Pair<BluetoothDevice, UUID> currentRead = mCurrentRead;
mExecutor.submit(
new Runnable() {
@Override
public void run() {
mHandler.onServiceReadFailed(currentRead.first, currentRead.second);
}
});
maybeReadNextService();
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState != BluetoothGatt.STATE_CONNECTED) {
// Connection is disconnected. Release it.
gatt.close();
return;
}
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "connectGatt failed: " + mCurrentRead.first + " , status: " + status);
cancelAndMaybeReadNextService();
return;
}
if (!gatt.requestMtu(MTU)) {
Log.e(TAG, "requestMtu failed: " + mCurrentRead.first);
// Try to discover services although requesting MTU fails.
if (!gatt.discoverServices()) {
Log.e(TAG, "discoverServices failed: " + mCurrentRead.first);
cancelAndMaybeReadNextService();
}
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG, "requestMtu failed: " + mCurrentRead.first + ", status: " + status);
}
if (!gatt.discoverServices()) {
Log.e(TAG, "discoverServices failed: " + mCurrentRead.first);
cancelAndMaybeReadNextService();
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "discoverServices failed: " + mCurrentRead.first + ", status: " + status);
cancelAndMaybeReadNextService();
return;
}
mCurrentService = gatt.getService(mCurrentRead.second);
if (mCurrentService == null) {
Log.e(
TAG,
"service not found: " + mCurrentRead.first + ", uuid: " + mCurrentRead.second);
cancelAndMaybeReadNextService();
return;
}
mCurrentCharacteristicIterator = mCurrentService.getCharacteristics().iterator();
maybeReadNextCharacteristic();
}
private void maybeReadNextCharacteristic() {
if (!mCurrentCharacteristicIterator.hasNext()) {
// All characteristics have been read. Finish the current read.
finishAndMaybeReadNextService();
return;
}
BluetoothGattCharacteristic characteristic = mCurrentCharacteristicIterator.next();
if (!mCurrentGatt.readCharacteristic(characteristic)) {
Log.e(TAG, "readCharacteristic failed: " + mCurrentRead.first);
cancelAndMaybeReadNextService();
}
}
@Override
public void onCharacteristicRead(
BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "readCharacteristic failed: " + mCurrentRead.first + ", status: " + status);
cancelAndMaybeReadNextService();
return;
}
maybeReadNextCharacteristic();
}
}