2021-01-10 23:37:09 +01:00
/ * Copyright ( C ) 2015 - 2021 Andreas Böhler , Andreas Shimokawa , Carsten
2019-12-06 22:49:44 +01:00
Pfeiffer , Daniel Dakhno , Daniele Gobbetti , JohnnySun , José Rebelo
2017-03-10 14:53:19 +01:00
This file is part of Gadgetbridge .
Gadgetbridge is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Gadgetbridge is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>. */
2015-08-03 23:09:49 +02:00
package nodomain.freeyourgadget.gadgetbridge.service.btle ;
2015-04-19 02:37:29 +02:00
2019-01-29 11:00:58 +01:00
import android.bluetooth.BluetoothDevice ;
2015-04-26 00:53:48 +02:00
import android.bluetooth.BluetoothGatt ;
import android.bluetooth.BluetoothGattCharacteristic ;
2015-05-24 14:39:36 +02:00
import android.bluetooth.BluetoothGattDescriptor ;
2015-04-26 00:53:48 +02:00
import android.bluetooth.BluetoothGattService ;
2021-12-24 15:20:57 +01:00
import android.content.Intent ;
2022-06-04 22:20:28 +02:00
import android.location.Location ;
2015-04-26 00:53:48 +02:00
2015-05-18 20:56:19 +02:00
import org.slf4j.Logger ;
2015-04-19 02:37:29 +02:00
import java.io.IOException ;
2016-07-25 00:00:22 +02:00
import java.util.ArrayList ;
2015-04-19 02:37:29 +02:00
import java.util.HashMap ;
import java.util.HashSet ;
import java.util.List ;
2017-03-09 22:46:08 +01:00
import java.util.Map ;
2015-04-19 02:37:29 +02:00
import java.util.Set ;
import java.util.UUID ;
2016-10-20 21:42:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.Logging ;
2017-03-09 22:46:08 +01:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice ;
2021-12-04 16:55:09 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.Reminder ;
2022-05-09 20:47:08 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock ;
2015-08-03 23:09:49 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport ;
2016-04-03 21:41:52 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile ;
2015-04-19 02:37:29 +02:00
/ * *
2015-08-02 00:12:21 +02:00
* Abstract base class for all devices connected through Bluetooth Low Energy ( LE ) aka
* Bluetooth Smart .
2015-08-07 16:59:52 +02:00
* < p / >
2015-08-02 00:12:21 +02:00
* The connection to the device and all communication is made with a generic { @link BtLEQueue } .
2019-01-29 11:00:58 +01:00
* Messages to the device are encoded as { @link BtLEAction actions } or { @link BtLEServerAction actions }
* that are grouped with a { @link Transaction } or { @link ServerTransaction } and sent via { @link BtLEQueue } .
2015-08-02 00:12:21 +02:00
*
2015-04-19 02:37:29 +02:00
* @see TransactionBuilder
* @see BtLEQueue
* /
2019-01-29 11:00:58 +01:00
public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback , GattServerCallback {
2015-04-19 02:37:29 +02:00
private BtLEQueue mQueue ;
2017-03-09 22:46:08 +01:00
private Map < UUID , BluetoothGattCharacteristic > mAvailableCharacteristics ;
2015-11-23 23:04:46 +01:00
private final Set < UUID > mSupportedServices = new HashSet < > ( 4 ) ;
2019-01-29 11:00:58 +01:00
private final Set < BluetoothGattService > mSupportedServerServices = new HashSet < > ( 4 ) ;
2016-10-20 21:42:36 +02:00
private Logger logger ;
2015-04-19 02:37:29 +02:00
2016-10-20 21:42:36 +02:00
private final List < AbstractBleProfile < ? > > mSupportedProfiles = new ArrayList < > ( ) ;
2015-08-07 16:59:52 +02:00
public static final String BASE_UUID = " 0000%s-0000-1000-8000-00805f9b34fb " ; //this is common for all BTLE devices. see http://stackoverflow.com/questions/18699251/finding-out-android-bluetooth-le-gatt-profiles
2017-03-09 22:46:08 +01:00
private final Object characteristicsMonitor = new Object ( ) ;
2015-08-07 16:59:52 +02:00
2016-10-20 21:42:36 +02:00
public AbstractBTLEDeviceSupport ( Logger logger ) {
this . logger = logger ;
if ( logger = = null ) {
throw new IllegalArgumentException ( " logger must not be null " ) ;
}
}
2015-04-19 02:37:29 +02:00
@Override
public boolean connect ( ) {
if ( mQueue = = null ) {
2019-01-29 11:00:58 +01:00
mQueue = new BtLEQueue ( getBluetoothAdapter ( ) , getDevice ( ) , this , this , getContext ( ) , mSupportedServerServices ) ;
2016-04-28 23:17:13 +02:00
mQueue . setAutoReconnect ( getAutoReconnect ( ) ) ;
2015-04-19 02:37:29 +02:00
}
return mQueue . connect ( ) ;
}
2016-04-28 23:17:13 +02:00
@Override
public void setAutoReconnect ( boolean enable ) {
super . setAutoReconnect ( enable ) ;
if ( mQueue ! = null ) {
mQueue . setAutoReconnect ( enable ) ;
}
}
2015-04-19 02:37:29 +02:00
/ * *
* Subclasses should populate the given builder to initialize the device ( if necessary ) .
2015-04-19 11:28:03 +02:00
*
2015-04-19 02:37:29 +02:00
* @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 ( ) ;
2015-05-22 23:15:45 +02:00
mQueue = null ;
2015-04-19 02:37:29 +02:00
}
}
2015-04-19 11:28:03 +02:00
2016-09-20 23:09:42 +02:00
public TransactionBuilder createTransactionBuilder ( String taskName ) {
2015-05-24 00:11:14 +02:00
return new TransactionBuilder ( taskName ) ;
}
2015-04-19 02:37:29 +02:00
/ * *
* Send commands like this to the device :
* < p >
2019-01-03 00:44:34 +01:00
* < code > performInitialized ( " sms notification " ) . write ( someCharacteristic , someByteArray ) . queue ( getQueue ( ) ) ; < / code >
2015-04-19 02:37:29 +02:00
* < / p >
2019-01-03 00:44:34 +01:00
* This will asynchronously
* < ul >
* < li > connect to the device ( if necessary ) < / li >
* < li > initialize the device ( if necessary ) < / li >
* < li > execute the commands collected with the returned transaction builder < / li >
* < / ul >
2015-04-19 11:28:03 +02:00
*
2015-04-19 02:37:29 +02:00
* @see # performConnected ( Transaction )
* @see # initializeDevice ( TransactionBuilder )
* /
2015-08-18 00:28:17 +02:00
public TransactionBuilder performInitialized ( String taskName ) throws IOException {
2015-04-19 02:37:29 +02:00
if ( ! isConnected ( ) ) {
if ( ! connect ( ) ) {
throw new IOException ( " 1: Unable to connect to device: " + getDevice ( ) ) ;
}
}
if ( ! isInitialized ( ) ) {
// first, add a transaction that performs device initialization
2015-05-24 00:11:14 +02:00
TransactionBuilder builder = createTransactionBuilder ( " Initialize device " ) ;
2015-05-28 00:26:41 +02:00
builder . add ( new CheckInitializedAction ( gbDevice ) ) ;
2015-04-19 02:37:29 +02:00
initializeDevice ( builder ) . queue ( getQueue ( ) ) ;
}
2015-05-24 00:11:14 +02:00
return createTransactionBuilder ( taskName ) ;
2015-04-19 02:37:29 +02:00
}
2019-01-29 11:00:58 +01:00
public ServerTransactionBuilder createServerTransactionBuilder ( String taskName ) {
return new ServerTransactionBuilder ( taskName ) ;
}
public ServerTransactionBuilder performServer ( String taskName ) throws IOException {
if ( ! isConnected ( ) ) {
if ( ! connect ( ) ) {
throw new IOException ( " 1: Unable to connect to device: " + getDevice ( ) ) ;
}
}
return createServerTransactionBuilder ( taskName ) ;
}
2015-04-19 02:37:29 +02:00
/ * *
2019-01-03 00:44:34 +01:00
* Ensures that the device is connected and ( only then ) performs the actions of the given
* transaction builder .
*
* In contrast to { @link # performInitialized ( String ) } , no initialization sequence is performed
* with the device , only the actions of the given builder are executed .
2015-04-19 02:37:29 +02:00
* @param transaction
* @throws IOException
* @see { @link # performInitialized ( String ) }
* /
2016-09-20 23:09:42 +02:00
public void performConnected ( Transaction transaction ) throws IOException {
2015-04-19 02:37:29 +02:00
if ( ! isConnected ( ) ) {
if ( ! connect ( ) ) {
throw new IOException ( " 2: Unable to connect to device: " + getDevice ( ) ) ;
}
}
getQueue ( ) . add ( transaction ) ;
}
2016-09-20 23:09:42 +02:00
/ * *
* Performs the actions of the given transaction as soon as possible ,
* that is , before any other queued transactions , but after the actions
* of the currently executing transaction .
* @param builder
* /
public void performImmediately ( TransactionBuilder builder ) throws IOException {
if ( ! isConnected ( ) ) {
throw new IOException ( " Not connected to device: " + getDevice ( ) ) ;
}
getQueue ( ) . insert ( builder . getTransaction ( ) ) ;
}
2015-04-19 02:37:29 +02:00
public BtLEQueue getQueue ( ) {
return mQueue ;
}
2015-04-19 11:28:03 +02:00
2015-04-19 02:37:29 +02:00
/ * *
* Subclasses should call this method to add services they support .
* Only supported services will be queried for characteristics .
2015-04-19 11:28:03 +02:00
*
2015-04-19 02:37:29 +02:00
* @param aSupportedService
* @see # getCharacteristic ( UUID )
* /
protected void addSupportedService ( UUID aSupportedService ) {
mSupportedServices . add ( aSupportedService ) ;
}
2016-07-25 00:00:22 +02:00
protected void addSupportedProfile ( AbstractBleProfile < ? > profile ) {
mSupportedProfiles . add ( profile ) ;
}
2019-01-29 11:00:58 +01:00
/ * *
* Subclasses should call this method to add server services they support .
* @param service
* /
protected void addSupportedServerService ( BluetoothGattService service ) {
mSupportedServerServices . add ( service ) ;
}
2015-04-19 02:37:29 +02:00
/ * *
* Returns the characteristic matching the given UUID . Only characteristics
* are returned whose service is marked as supported .
2015-04-19 11:28:03 +02:00
*
2015-04-19 02:37:29 +02:00
* @param uuid
* @return the characteristic for the given UUID or < code > null < / code >
* @see # addSupportedService ( UUID )
* /
2015-08-18 00:08:22 +02:00
public BluetoothGattCharacteristic getCharacteristic ( UUID uuid ) {
2017-03-09 22:46:08 +01:00
synchronized ( characteristicsMonitor ) {
if ( mAvailableCharacteristics = = null ) {
return null ;
}
return mAvailableCharacteristics . get ( uuid ) ;
2015-04-19 02:37:29 +02:00
}
}
private void gattServicesDiscovered ( List < BluetoothGattService > discoveredGattServices ) {
if ( discoveredGattServices = = null ) {
2016-10-20 21:42:36 +02:00
logger . warn ( " No gatt services discovered: null! " ) ;
2015-04-19 02:37:29 +02:00
return ;
}
Set < UUID > supportedServices = getSupportedServices ( ) ;
2017-03-09 22:46:08 +01:00
Map < UUID , BluetoothGattCharacteristic > newCharacteristics = new HashMap < > ( ) ;
2015-04-19 02:37:29 +02:00
for ( BluetoothGattService service : discoveredGattServices ) {
if ( supportedServices . contains ( service . getUuid ( ) ) ) {
2016-10-20 21:42:36 +02:00
logger . debug ( " discovered supported service: " + BleNamesResolver . resolveServiceName ( service . getUuid ( ) . toString ( ) ) + " : " + service . getUuid ( ) ) ;
2015-04-19 02:37:29 +02:00
List < BluetoothGattCharacteristic > characteristics = service . getCharacteristics ( ) ;
if ( characteristics = = null | | characteristics . isEmpty ( ) ) {
2016-10-20 21:42:36 +02:00
logger . warn ( " Supported LE service " + service . getUuid ( ) + " did not return any characteristics " ) ;
2015-04-19 02:37:29 +02:00
continue ;
}
2015-09-25 17:45:06 +02:00
HashMap < UUID , BluetoothGattCharacteristic > intmAvailableCharacteristics = new HashMap < > ( characteristics . size ( ) ) ;
2015-04-19 02:37:29 +02:00
for ( BluetoothGattCharacteristic characteristic : characteristics ) {
2015-09-25 17:45:06 +02:00
intmAvailableCharacteristics . put ( characteristic . getUuid ( ) , characteristic ) ;
2016-10-20 21:42:36 +02:00
logger . info ( " characteristic: " + BleNamesResolver . resolveCharacteristicName ( characteristic . getUuid ( ) . toString ( ) ) + " : " + characteristic . getUuid ( ) ) ;
2015-04-19 02:37:29 +02:00
}
2017-03-09 22:46:08 +01:00
newCharacteristics . putAll ( intmAvailableCharacteristics ) ;
synchronized ( characteristicsMonitor ) {
mAvailableCharacteristics = newCharacteristics ;
}
2016-07-18 23:55:44 +02:00
} else {
2016-10-20 21:42:36 +02:00
logger . debug ( " discovered unsupported service: " + BleNamesResolver . resolveServiceName ( service . getUuid ( ) . toString ( ) ) + " : " + service . getUuid ( ) ) ;
2015-04-19 02:37:29 +02:00
}
}
}
protected Set < UUID > getSupportedServices ( ) {
return mSupportedServices ;
}
2016-10-20 21:42:36 +02:00
/ * *
* Utility method that may be used to log incoming messages when we don ' t know how to deal with them yet .
*
* @param value
* /
public void logMessageContent ( byte [ ] value ) {
logger . info ( " RECEIVED DATA WITH LENGTH: " + ( ( value ! = null ) ? value . length : " (null) " ) ) ;
Logging . logBytes ( logger , value ) ;
}
2015-04-19 02:37:29 +02:00
// default implementations of event handler methods (gatt callbacks)
@Override
public void onConnectionStateChange ( BluetoothGatt gatt , int status , int newState ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
profile . onConnectionStateChange ( gatt , status , newState ) ;
}
2015-04-19 02:37:29 +02:00
}
2015-04-19 11:28:03 +02:00
2015-04-19 02:37:29 +02:00
@Override
public void onServicesDiscovered ( BluetoothGatt gatt ) {
2016-05-26 19:03:38 +02:00
gattServicesDiscovered ( gatt . getServices ( ) ) ;
2017-03-09 22:46:08 +01:00
if ( getDevice ( ) . getState ( ) . compareTo ( GBDevice . State . INITIALIZING ) > = 0 ) {
logger . warn ( " Services discovered, but device state is already " + getDevice ( ) . getState ( ) + " for device: " + getDevice ( ) + " , so ignoring " ) ;
return ;
}
2015-05-24 00:11:14 +02:00
initializeDevice ( createTransactionBuilder ( " Initializing device " ) ) . queue ( getQueue ( ) ) ;
2015-04-19 02:37:29 +02:00
}
2015-04-19 11:28:03 +02:00
2015-04-19 02:37:29 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicRead ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic , int status ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
2016-07-28 23:04:37 +02:00
if ( profile . onCharacteristicRead ( gatt , characteristic , status ) ) {
return true ;
}
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2015-04-19 02:37:29 +02:00
}
2015-04-19 11:28:03 +02:00
2015-04-19 02:37:29 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicWrite ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic , int status ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
2016-07-28 23:04:37 +02:00
if ( profile . onCharacteristicWrite ( gatt , characteristic , status ) ) {
return true ;
}
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2015-04-19 02:37:29 +02:00
}
2015-04-19 11:28:03 +02:00
2015-05-24 14:39:36 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onDescriptorRead ( BluetoothGatt gatt , BluetoothGattDescriptor descriptor , int status ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
2016-07-28 23:04:37 +02:00
if ( profile . onDescriptorRead ( gatt , descriptor , status ) ) {
return true ;
}
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2015-05-24 14:39:36 +02:00
}
@Override
2016-07-28 23:04:37 +02:00
public boolean onDescriptorWrite ( BluetoothGatt gatt , BluetoothGattDescriptor descriptor , int status ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
2016-07-28 23:04:37 +02:00
if ( profile . onDescriptorWrite ( gatt , descriptor , status ) ) {
return true ;
}
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2015-05-24 14:39:36 +02:00
}
2015-04-19 02:37:29 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicChanged ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
2016-07-28 23:04:37 +02:00
if ( profile . onCharacteristicChanged ( gatt , characteristic ) ) {
return true ;
}
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2015-04-19 02:37:29 +02:00
}
2015-04-19 11:28:03 +02:00
2015-04-22 22:50:35 +02:00
@Override
2015-04-19 02:37:29 +02:00
public void onReadRemoteRssi ( BluetoothGatt gatt , int rssi , int status ) {
2016-07-25 00:00:22 +02:00
for ( AbstractBleProfile profile : mSupportedProfiles ) {
profile . onReadRemoteRssi ( gatt , rssi , status ) ;
}
2015-04-19 02:37:29 +02:00
}
2018-07-28 17:23:58 +02:00
2022-08-18 23:03:28 +02:00
@Override
2022-10-22 21:53:45 +02:00
public void onFindPhone ( boolean start ) {
2022-08-18 23:03:28 +02:00
}
2019-11-14 04:53:19 +01:00
@Override
public void onMtuChanged ( BluetoothGatt gatt , int mtu , int status ) {
}
2018-07-28 17:23:58 +02:00
@Override
public void onSetFmFrequency ( float frequency ) {
}
@Override
public void onSetLedColor ( int color ) {
}
2019-01-29 11:00:58 +01:00
2021-12-24 15:20:57 +01:00
@Override
public void onPowerOff ( ) {
}
2022-05-05 15:14:15 +02:00
@Override
public void onSetPhoneVolume ( final float volume ) {
}
2022-06-04 22:20:28 +02:00
@Override
public void onSetGpsLocation ( Location location ) {
}
2021-12-04 16:55:09 +01:00
@Override
public void onSetReminders ( ArrayList < ? extends Reminder > reminders ) {
}
2022-05-09 20:47:08 +02:00
@Override
public void onSetWorldClocks ( ArrayList < ? extends WorldClock > clocks ) {
}
2019-01-29 11:00:58 +01:00
@Override
public void onConnectionStateChange ( BluetoothDevice device , int status , int newState ) {
}
@Override
public boolean onCharacteristicReadRequest ( BluetoothDevice device , int requestId , int offset , BluetoothGattCharacteristic characteristic ) {
return false ;
}
@Override
public boolean onCharacteristicWriteRequest ( BluetoothDevice device , int requestId , BluetoothGattCharacteristic characteristic , boolean preparedWrite , boolean responseNeeded , int offset , byte [ ] value ) {
return false ;
}
@Override
public boolean onDescriptorReadRequest ( BluetoothDevice device , int requestId , int offset , BluetoothGattDescriptor descriptor ) {
return false ;
}
@Override
public boolean onDescriptorWriteRequest ( BluetoothDevice device , int requestId , BluetoothGattDescriptor descriptor , boolean preparedWrite , boolean responseNeeded , int offset , byte [ ] value ) {
return false ;
}
2015-04-19 02:37:29 +02:00
}