{ ******************************************************* }
{ }
{ CodeGear Delphi Runtime Library }
{ Copyright(c) 2014-2022 Embarcadero Technologies, Inc. }
{ All rights reserved }
{ }
{ ******************************************************* }
unit System.Android.Bluetooth;
interface
{$IF Defined(ANDROID)}
{$I ESFWX.inc}
uses
System.Bluetooth;
{$SCOPEDENUMS ON}
{$DEFINE BLUETOOTH_CLASSIC}
{$DEFINE BLUETOOTH_LE}
type
{$IFDEF BLUETOOTH_CLASSIC}
// --------------------------------------------------------------------- //
TPlatformBluetoothClassicManager = class(TBluetoothManager)
protected
class function GetBluetoothManager: TBluetoothManager; override;
end;
{$ENDIF BLUETOOTH_CLASSIC}
{$IFDEF BLUETOOTH_LE}
// --------------------------------------------------------------------- //
TPlatformBluetoothLEManager = class(TBluetoothLEManager)
protected
class function GetBluetoothManager: TBluetoothLEManager; override;
end;
/// Extra BLE Scan properties for Android
IScanSettingsOptions = interface
['{353170C7-B913-4FF9-A0E6-C5825A9BAC71}']
/// Get Function
function GetCALLBACK_TYPE_ALL_MATCHES: Integer;
/// Get Function
function GetCALLBACK_TYPE_FIRST_MATCH: Integer;
/// Get Function
function GetCALLBACK_TYPE_MATCH_LOST: Integer;
/// Get Function
function GetMATCH_MODE_AGGRESSIVE: Integer;
/// Get Function
function GetMATCH_MODE_STICKY: Integer;
/// Get Function
function GetMATCH_NUM_FEW_ADVERTISEMENT: Integer;
/// Get Function
function GetMATCH_NUM_MAX_ADVERTISEMENT: Integer;
/// Get Function
function GetMATCH_NUM_ONE_ADVERTISEMENT: Integer;
/// Get Function
function GetSCAN_MODE_BALANCED: Integer;
/// Get Function
function GetSCAN_MODE_LOW_LATENCY: Integer;
/// Get Function
function GetSCAN_MODE_LOW_POWER: Integer;
/// Get Function
function GetSCAN_MODE_OPPORTUNISTIC: Integer;
/// From API 21. Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
/// If no filter is active, all advertisement packets are reported.
///
property CALLBACK_TYPE_ALL_MATCHES: Integer read GetCALLBACK_TYPE_ALL_MATCHES;
/// From API 23. A result callback is only triggered for the first advertisement packet received that
/// matches the filter criteria.
///
property CALLBACK_TYPE_FIRST_MATCH: Integer read GETCALLBACK_TYPE_FIRST_MATCH;
/// From API 23. Receive a callback when advertisements are no longer received from a device that has
/// been previously reported by a first match callback.
///
property CALLBACK_TYPE_MATCH_LOST: Integer read GetCALLBACK_TYPE_MATCH_LOST;
/// From API 23. In Aggressive mode, hw will determine a match sooner even with feeble signal strength
/// and few number of sightings/match in a duration.
///
property MATCH_MODE_AGGRESSIVE: Integer read GetMATCH_MODE_AGGRESSIVE;
/// From API 23. For sticky mode, higher threshold of signal strength and sightings is required before
/// reporting by hw
///
property MATCH_MODE_STICKY: Integer read GetMATCH_MODE_STICKY;
/// From API 23. Match few advertisement per filter, depends on current capability and availibility
/// of the resources in hw
///
property MATCH_NUM_FEW_ADVERTISEMENT: Integer read GetMATCH_NUM_FEW_ADVERTISEMENT;
/// From API 23. Match as many advertisement per filter as hw could allow, depends on current capability
/// and availibility of the resources in hw
///
property MATCH_NUM_MAX_ADVERTISEMENT: Integer read GetMATCH_NUM_MAX_ADVERTISEMENT;
/// From API 23. Match one advertisement per filter
property MATCH_NUM_ONE_ADVERTISEMENT: Integer read GetMATCH_NUM_ONE_ADVERTISEMENT;
/// From API 21. Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate
/// that provides a good trade-off between scan frequency and power consumption.
///
property SCAN_MODE_BALANCED: Integer read GetSCAN_MODE_BALANCED;
/// From API 21. Scan using highest duty cycle. It's recommended to only use this mode when the application
/// is running in the foreground.
///
property SCAN_MODE_LOW_LATENCY: Integer read GetSCAN_MODE_LOW_LATENCY;
/// From API 21. Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes
/// the least power.
///
property SCAN_MODE_LOW_POWER: Integer read GetSCAN_MODE_LOW_POWER;
/// From API 23. A special Bluetooth LE scan mode. Applications using this scan mode will passively listen
/// for other scan results without starting BLE scans themselves.
///
property SCAN_MODE_OPPORTUNISTIC: Integer read GetSCAN_MODE_OPPORTUNISTIC;
end;
/// Extra BLE properties for Android
IExtraBLEAndroidProperties = interface
['{C14C69E8-C51B-4CF8-839B-FDE9CB96F15D}']
/// Get Function
function GetScanSettingsOptions: IScanSettingsOptions;
/// Get Function
function GetScanSettings: Integer;
/// Set procedure
procedure SetScanSettings(Value: Integer);
/// From API 21. Constants to set ScanSettings
property ScanSettingsOptions: IScanSettingsOptions read GetScanSettingsOptions;
/// From API 21. Settings for the BLE Scanner.
property ScanSettings: Integer read GetScanSettings write SetScanSettings;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
{$ENDIF BLUETOOTH_LE}
{$ENDIF}
implementation
{$IF Defined(ANDROID)}
uses
Androidapi.JNI,
Androidapi.JNIBridge,
Androidapi.JNI.App,
Androidapi.JNI.Embarcadero,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Os,
Androidapi.NativeActivity,
Androidapi.Helpers,
Androidapi.JNI.Bluetooth,
System.Classes,
System.Messaging,
System.SysUtils,
System.SyncObjs,
System.TypInfo,
System.NetConsts,
System.Generics.Collections,
Androidapi.Looper,
Androidapi.AppGlue,
System.Types,
System.Rtti,
FMX.Types,
FMX.Forms,
ESFWX.esutilities,
uEsBleManager;
// --------------------------------------------------------------------- //
// Helper functions
// --------------------------------------------------------------------- //
{$IF Defined(DEBUG) and Defined(ES_USE_BTAPI_TRACE)}
procedure ES_BTAPI_TRACE(const fmt : UnicodeString); inline; overload;
begin
ES_DEBUG_TRACE(fmt);
end;
//---------------------------------------------------------------------------
procedure ES_BTAPI_TRACE(const fmt : UnicodeString; const params: array of const); overload;
begin
ES_DEBUG_TRACE(fmt, params);
end;
{$ELSE}
procedure ES_BTAPI_TRACE(const fmt : UnicodeString); inline; overload;
begin
end;
//---------------------------------------------------------------------------
procedure ES_BTAPI_TRACE(const fmt : UnicodeString; const params: array of const); overload;
begin
end;
{$ENDIF}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
function BluetoothUuidToJUuid(const AUuid: TBluetoothUUID): JUUID;
var
LAux: string;
begin
LAux := AUuid.ToString;
LAux := LAux.Substring(
1,
LAux.Length - 2);
Result := TJUUID.JavaClass.fromString(StringToJString(LAux));
end;
function JUuidToBluetoothUuid(const AJUuid: JUUID): TBluetoothUUID;
begin
Result := TBluetoothUUID.Create('{' + JStringToString(AJUuid.toString) + '}');
end;
// --------------------------------------------------------------------- //
function BluetoothUUIDsListToJavaArrayUUID(const BluetoothUUIDsList: TBluetoothUUIDsList): TJavaObjectArray;
var
I: Integer;
begin
if BluetoothUUIDsList <> nil then
begin
Result := TJavaObjectArray.Create(BluetoothUUIDsList.Count);
for I := 0 to BluetoothUUIDsList.Count - 1 do
Result.Items[I] := BluetoothUuidToJUuid(BluetoothUUIDsList[I]);
end
else
Result := nil;
end;
// --------------------------------------------------------------------- //
procedure InternalProcessMessages;
begin
TThread.Sleep(1);
end;
// --------------------------------------------------------------------- //
function CheckApiMethod(const AClassName, AMethodName, ASig: string): Boolean;
var
LJClass: JNIClass;
begin
LJClass := nil;
try
LJClass := JNIClass(TJNIResolver.GetJavaClassID(AClassName));
if LJClass <> nil then
Result := TJNIResolver.GetJavaMethodID(LJClass, AMethodName, ASig) <> nil
else
Result := False
finally
if LJClass <> nil then
TJNIResolver.DeleteLocalRef(LJClass);
end;
end;
// --------------------------------------------------------------------- //
// End helper functions
// --------------------------------------------------------------------- //
type
TAndroidBluetoothAdapter = class;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TBluetoothBroadcastListener = class(TJavaLocal, JFMXBroadcastReceiverListener)
private
[Weak]
FAdapter: TAndroidBluetoothAdapter;
public
constructor Create(const AnAdapter: TAndroidBluetoothAdapter);
procedure onReceive(context: JContext; intent: JIntent); cdecl;
end;
// --------------------------------------------------------------------- //
TAndroidBluetoothManager = class(TPlatformBluetoothClassicManager)
private
FAdapter: TBluetoothAdapter;
protected
FContext: JContext;
FJManager: JBluetoothManager;
constructor Create;
destructor Destroy; override;
function DoGetClassicAdapter: TBluetoothAdapter; override;
function GetAdapterState: TBluetoothAdapterState;
function GetConnectionState: TBluetoothConnectionState; override;
function DoEnableBluetooth: Boolean; override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothAdapter = class(TBluetoothAdapter)
private type
TThreadTimer = class(TThread)
private
[Weak]
FAdapter: TBluetoothAdapter;
FTimeout: Cardinal;
FOnTimer: TDiscoveryEndEvent;
FDiscovering: Boolean;
FEvent: TEvent;
procedure Cancel;
public
constructor Create(const AnAdapter: TBluetoothAdapter; const AEvent: TDiscoveryEndEvent; Timeout: Cardinal); overload;
destructor Destroy; override;
procedure Execute; override;
end;
private
FIntentFilter: JIntentFilter;
FContext: JContext;
FJAdapter: JBluetoothAdapter;
FBroadcastReceiver: JFMXBroadcastReceiver;
FBroadcastListener: TBluetoothBroadcastListener;
FWaitingCallback: Boolean;
FRequestEnableCallback: Boolean;
FDiscoveryThreadTimer: TThreadTimer;
FDiscoveryCancelled: Boolean;
FOldScanMode: Integer;
function GetAdapterName: string; override;
function GetAddress: TBluetoothMacAddress; override;
function GetPairedDevices: TBluetoothDeviceList; override;
function GetScanMode: TBluetoothScanMode; override;
function GetState: TBluetoothAdapterState; override;
procedure SetAdapterName(const Value: string); override;
procedure ResultCallback(const Sender: TObject; const M: TMessage);
// Socket management functions
function DoCreateServerSocket(const AName: string; const AUUID: TGUID; Secure: Boolean): TBluetoothServerSocket; override;
// Device Discovery
procedure DoStartDiscovery(Timeout: Cardinal); override;
procedure DoCancelDiscovery; override;
procedure DoStartDiscoverable(Timeout: Cardinal); override;
function DoPair(const ADevice: TBluetoothDevice): Boolean; override;
function DoUnPair(const ADevice: TBluetoothDevice): Boolean; override;
public
constructor Create(const AManager: TBluetoothManager; const AJAdapter: JBluetoothAdapter);
destructor Destroy; override;
function GetRemoteDevice(const AnAddress: TBluetoothMacAddress): TBluetoothDevice; // override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TScanSettingsOptions = class(TInterfacedObject, IScanSettingsOptions)
const
CALLBACK_TYPE_ALL_MATCHES = 1;
CALLBACK_TYPE_FIRST_MATCH = 2;
CALLBACK_TYPE_MATCH_LOST = 4;
MATCH_MODE_AGGRESSIVE = 1;
MATCH_MODE_STICKY = 2;
MATCH_NUM_FEW_ADVERTISEMENT = 2;
MATCH_NUM_MAX_ADVERTISEMENT = 3;
MATCH_NUM_ONE_ADVERTISEMENT = 1;
SCAN_MODE_BALANCED = 1;
SCAN_MODE_LOW_LATENCY = 2;
SCAN_MODE_LOW_POWER = 0;
SCAN_MODE_OPPORTUNISTIC = -1;
public
function GetCALLBACK_TYPE_ALL_MATCHES: Integer;
function GetCALLBACK_TYPE_FIRST_MATCH: Integer;
function GetCALLBACK_TYPE_MATCH_LOST: Integer;
function GetMATCH_MODE_AGGRESSIVE: Integer;
function GetMATCH_MODE_STICKY: Integer;
function GetMATCH_NUM_FEW_ADVERTISEMENT: Integer;
function GetMATCH_NUM_MAX_ADVERTISEMENT: Integer;
function GetMATCH_NUM_ONE_ADVERTISEMENT: Integer;
function GetSCAN_MODE_BALANCED: Integer;
function GetSCAN_MODE_LOW_LATENCY: Integer;
function GetSCAN_MODE_LOW_POWER: Integer;
function GetSCAN_MODE_OPPORTUNISTIC: Integer;
end;
TAndroidBluetoothLEManager = class(TPlatformBluetoothLEManager, IExtraBLEAndroidProperties)
private
FAdapter: TBluetoothLEAdapter;
FIScanSettingsOptions: IScanSettingsOptions;
FScanSettings: Integer;
protected
FContext: JContext;
FJManager: JBluetoothManager;
constructor Create;
destructor Destroy; override;
function DoGetAdapter: TBluetoothLEAdapter; override;
function GetAdapterState: TBluetoothAdapterState;
function GetConnectionState: TBluetoothConnectionState; override;
function DoGetGattServer: TBluetoothGattServer; override;
function DoGetSupportsGattClient: Boolean; override;
function DoGetSupportsGattServer: Boolean; override;
function DoEnableBluetooth: Boolean; override;
function GetScanSettings: Integer;
procedure SetScanSettings(Value: Integer);
function GetScanSettingsOptions: IScanSettingsOptions;
public
property ScanSettings: Integer read GetScanSettings write SetScanSettings;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothLEAdapter = class;
TBluetoothLEBroadcastListener = class(TJavaLocal, JFMXBroadcastReceiverListener)
private
[Weak]
m_adapter: TAndroidBluetoothLEAdapter;
public
constructor Create(const adapter: TAndroidBluetoothLEAdapter);
procedure onReceive(context: JContext; intent: JIntent); cdecl;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothLEAdapter = class(TBluetoothLEAdapter)
private type
TThreadTimer = class(TThread)
private
[Weak]
FAdapter: TBluetoothLEAdapter;
FTimeout: Cardinal;
FOnTimer: TDiscoveryLEEndEvent;
FEvent: TEvent;
public
constructor Create(const AnAdapter: TBluetoothLEAdapter; const AEvent: TDiscoveryLEEndEvent; Timeout: Cardinal); overload;
destructor Destroy; override;
procedure Execute; override;
end;
public
type
Activity = (
InitiatingScan,
Scanning
);
Activities = set of Activity;
private
FTimerThread: TThreadTimer;
FLeCallback: JBluetoothAdapter_LeScanCallback;
FJScanCallback: JRTLScanCallback;
FJScanListener: JRTLScanListener;
FIntentFilter : JIntentFilter;
FBroadcastListener: TBluetoothLEBroadcastListener;
FBroadcastReceiver: JFMXBroadcastReceiver;
FContext: JContext;
FJAdapter: JBluetoothAdapter;
FJScanner: JBluetoothLeScanner;
FRequestEnableCallback: Boolean;
FJScanSettings: JScanSettings;
FJArrayListOfAdvertiseData: JArrayList;
FStartScanFailed: Integer;
FScanSettings: Integer;
m_scanFilterList: TBluetoothLEScanFilterList;
[volatile]
m_state : Activities;
private
function GetAdapterName: string; override;
function GetAddress: TBluetoothMacAddress; override;
function GetScanMode: TBluetoothScanMode; override;
function GetState: TBluetoothAdapterState; override;
procedure SetAdapterName(const Value: string); override;
function isScanning() : Boolean;
// LE Device Discovery
function DoStartDiscovery(Timeout: Cardinal; const FilterUUIDList: TBluetoothUUIDsList;
const ascanFilterList: TBluetoothLEScanFilterList = nil): Boolean; override;
function DoStartDiscoveryRaw(const ascanFilterList: TBluetoothLEScanFilterList = nil;
Refresh: Boolean = True): Boolean; override;
procedure DoCancelDiscovery; override;
procedure StopScan;
procedure DoDiscoveryEnd(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList); override;
procedure ResultCallback(const Sender: TObject; const M: TMessage);
public
constructor Create(const AManager: TBluetoothLEManager; const AJAdapter: JBluetoothAdapter);
destructor Destroy; override;
function GetRemoteDevice(const AnAddress: TBluetoothMacAddress): TBluetoothLEDevice; // override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothSocket = class(TBluetoothSocket)
private type
TBluetoothSocketReader = class(TThread)
protected
FJBytes: TJavaArray;
[Weak]
FSocket: TAndroidBluetoothSocket;
FBufferSize: Integer;
FDestroying: Boolean;
public
constructor Create(const ASocket: TAndroidBluetoothSocket; ABuffSize: Integer);
procedure Execute; override;
destructor Destroy; override;
function GetBufferedDataSize: Integer; inline;
procedure GetBufferedData(const ABuffer: TBytes; AnOffset: Integer); inline;
end;
protected const
BUFFER_SIZE = 4096;
SocketEventTimeout = 1000;
protected
FJBytes: TJavaArray;
FJBluetoothSocket: JBluetoothSocket;
FJIStream: JInputStream;
FJOStream: JOutputStream;
FConnected: Boolean;
FDestroying: Boolean;
FReader: TBluetoothSocketReader;
FSocketEvent: TEvent;
FReaderEvent: TEvent;
function GetConnected: Boolean; override;
function GetRemoteDevice: TBluetoothDevice; // override;
procedure DoClose; override;
procedure DoConnect; override;
function DoReceiveData(ATimeout: Cardinal): TBytes; override;
procedure DoSendData(const AData: TBytes); override;
public
constructor Create(const ASocket: JBluetoothSocket);
destructor Destroy; override;
end;
TAndroidBluetoothServerSocket = class(TBluetoothServerSocket)
protected
FJServerSocket: JBluetoothServerSocket;
function DoAccept(Timeout: Cardinal): TBluetoothSocket; override;
procedure DoClose; override;
public
constructor Create(const AServerSocket: JBluetoothServerSocket);
destructor Destroy; override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TBluetoothLeScanCallback = class(TJavaLocal, JBluetoothAdapter_LeScanCallback)
private
[Weak]
FAdapter: TAndroidBluetoothLEAdapter;
public
constructor Create(const AnAdapter: TAndroidBluetoothLEAdapter);
procedure onLeScan(device: JBluetoothDevice; rssi: Integer; scanRecord: TJavaArray); cdecl;
end;
TScanCallback = class(TJavaLocal, JRTLScanListener)
private
[Weak]
FAdapter: TAndroidBluetoothLEAdapter;
public
constructor Create(const AnAdapter: TAndroidBluetoothLEAdapter);
procedure onBatchScanResults(results: JList); cdecl;
procedure onScanFailed(errorCode: Integer); cdecl;
procedure onScanResult(callbackType: Integer; result: Jle_ScanResult); cdecl;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothDevice = class(TBluetoothDevice)
private const
BluetoothServicesTimeout = 6600;
private
FJDevice: JBluetoothDevice;
protected
FEventUUIDS: TEvent;
FDiscoverServiceEvent: TDiscoverServiceEvent;
function GetOnDiscoverService: TDiscoverServiceEvent;
procedure SetOnDiscoverService(const Value: TDiscoverServiceEvent);
function GetAddress: TBluetoothMacAddress; override;
function GetDeviceName: string; override;
function GetPaired: Boolean; override;
function GetState: TBluetoothDeviceState; override;
function GetBluetoothType: TBluetoothType; override;
function GetClassDevice: Integer; override;
function GetClassDeviceMajor: Integer; override;
function DoGetServices: TBluetoothServiceList; override;
// Socket Management functions.
function DoCreateClientSocket(const AUUID: TGUID; Secure: Boolean): TBluetoothSocket; override;
public
constructor Create(const AJDevice: JBluetoothDevice);
destructor Destroy; override;
function Pair: Boolean;
property Address: TBluetoothMacAddress read GetAddress;
property DeviceName: string read GetDeviceName;
property OnDiscoverService: TDiscoverServiceEvent read GetOnDiscoverService write SetOnDiscoverService;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothLEDevice = class;
TAndroidBluetoothGattCharacteristic = class;
TAndroidBluetoothGattDescriptor = class;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothLEAdvertiseData = class(TBluetoothLEAdvertiseData)
private
FBluetoothGattServer: TBluetoothGattServer;
FJBluetoothLeAdvertiser: JBluetoothLeAdvertiser;
FJAdvertiseData_Builder: JAdvertiseData_Builder;
FJScanResponse_Builder: JAdvertiseData_Builder;
FJAdvertiseData: JAdvertiseData;
FJScanResponse: JAdvertiseData;
FJScanResult: Jle_ScanResult;
FJAdvertiseSettings_Builder: JAdvertiseSettings_Builder;
FJAdvertiseCallback: JRTLAdvertiseCallback;
FJAdvertiseListener: JRTLAdvertiseListener;
FDevice: TBluetoothLEDevice;
FAdapter: TBluetoothLEAdapter;
FEvent: TEvent;
FServiceUUIDChanged: Boolean;
FServiceDataChanged: Boolean;
protected
FAdvertising: Boolean;
FErrorCode: Integer;
function GetServiceUUIDs: TArray; override;
function GetServiceData: TArray; override;
procedure SetLocalName(const ALocalName: string); override;
function GetLocalName: string; override;
procedure SetTxPowerLevel(ATxPowerLevel: Integer); override;
function GetTxPowerLevel: Integer; override;
procedure SetManufacturerSpecificData(const AManufacturerSpecificData: TBytes); override;
function GetManufacturerSpecificData: TBytes; override;
procedure DoStartAdvertising;
procedure DoStopAdvertising;
procedure CreateAdvertiseDataJavaObjects;
public
constructor Create(const ABluetoothGattServer: TBluetoothGattServer; const AnAdapter: TBluetoothLEAdapter;
const ADevice: TBluetoothLEDevice = nil);
destructor Destroy; override;
function DoAddServiceUUID(const AServiceUUID: TBluetoothUUID): Boolean; override;
procedure DoRemoveServiceUUID(const AServiceUUID: TBluetoothUUID); override;
procedure DoClearServiceUUIDs; override;
function ContainsServiceUUID(const AServiceUUID: TBluetoothUUID): Boolean; override;
function DoAddServiceData(const AServiceUUID: TBluetoothUUID; const AData: TBytes): Boolean; override;
procedure DoRemoveServiceData(const AServiceUUID: TBluetoothUUID); override;
function GetDataForService(const AServiceUUID: TBluetoothUUID): TBytes; override;
procedure DoClearServiceData; override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothGattServer = class(TBluetoothGattServer)
private
FJGattServer: JBluetoothGattServer;
FJGattServerCallback: JRTLBluetoothGattServerCallback;
FJGattServerListener: JRTLBluetoothGattServerListener;
FLastRWrittenGattCharacteristic: TBluetoothGattCharacteristic;
function FindCharacteristic(const AJCharacteristic: JBluetoothGattCharacteristic): TAndroidBluetoothGattCharacteristic;
// procedure DoDisconnect(const ADevice: TBluetoothLEDevice);
procedure DoClientConfigurationWrite(const ADescriptor: TBluetoothGattDescriptor; const OldValue: TArray; const AClient: TBluetoothLEDevice);
protected
// Helper Functions
function FindDevice(const AJDevice: JBluetoothDevice): TBluetoothLEDevice;
function FindService(const AJService: JBluetoothGattService): TBluetoothGattService;
// Service Management.
function DoAddService(const AService: TBluetoothGattService): Boolean; override;
function DoCreateService(const AnUUID: TBluetoothUUID; AType: TBluetoothServiceType): TBluetoothGattService; override;
function DoCreateIncludedService(const AService: TBluetoothGattService; const AnUUID: TBluetoothUUID;
AType: TBluetoothServiceType): TBluetoothGattService; override;
// Characteristic Management.
function DoAddCharacteristic(const AService: TBluetoothGattService;
const ACharacteristic: TBluetoothGattCharacteristic): Boolean; override;
function DoCreateCharacteristic(const AService: TBluetoothGattService; const AnUUID: TBluetoothUUID;
const AProps: TBluetoothPropertyFlags; const ADescription: string = ''): TBluetoothGattCharacteristic; override;
// Descriptor Management.
function DoCreateDescriptor(const ACharacteristic: TBluetoothGattCharacteristic;
const AnUUID: TBluetoothUUID): TBluetoothGattDescriptor; override;
function DoCreateAdvertiseData: TBluetoothLEAdvertiseData; override;
function DoGetServices: TBluetoothGattServiceList; override;
procedure DoClearServices; override;
procedure DoClose; override;
function DoConnect(const ADevice: TBluetoothLEDevice; AutoConnect: Boolean): Boolean;
procedure DoCharacteristicReadRequest(const ADevice: TBluetoothLEDevice; ARequestId: Integer; AnOffset: Integer;
const AGattCharacteristic: TBluetoothGattCharacteristic); override;
procedure DoCharacteristicWriteRequest(const ADevice: TBluetoothLEDevice; ARequestId: Integer;
const AGattCharacteristic: TBluetoothGattCharacteristic; APreparedWrite: Boolean; AResponseNeeded: Boolean;
AnOffset: Integer; const AValue: TBytes); override;
procedure DoUpdateCharacteristicValue(const ACharacteristic: TBluetoothGattCharacteristic); override;
procedure DoDescriptorReadRequest(const ADevice: TBluetoothLEDevice; ARequestId: Integer; AnOffset: Integer;
const ADescriptor: TBluetoothGattDescriptor);
procedure DoDescriptorWriteRequest(const ADevice: TBluetoothLEDevice; ARequestId: Integer;
const ADescriptor: TBluetoothGattDescriptor; APreparedWrite: Boolean; AResponseNeeded: Boolean; AnOffset: Integer;
const AValue: TBytes);
// procedure DoConnectionStateChange(const ADevice: TBluetoothLEDevice; AStatus: Integer; ANewState: Integer); override;
procedure DoServiceAdded(AStatus: TBluetoothGattStatus; const AService: TBluetoothGattService); override;
procedure DoStartAdvertising; override;
procedure DoStopAdvertising; override;
function DoIsAdvertising: Boolean; override;
procedure DoExecuteWrite(const ADevice: TBluetoothLEDevice; ARequestId: Integer; Execute: Boolean);
procedure DoSendResponse(const ADevice: TBluetoothLEDevice; ARequestId: Integer; AStatus: Integer;
AnOffset: Integer; const AValue: TBytes);
public
constructor Create(const AManager: TAndroidBluetoothLEManager; const AnAdapter: TAndroidBluetoothLEAdapter);
destructor Destroy; override;
end;
// --------------------------------------------------------------------- //
TAndroidBluetoothGattServerListener = class(TJavaLocal, JRTLBluetoothGattServerListener)
protected
[Weak]
FGattServer: TAndroidBluetoothGattServer;
public
constructor Create(const AServer: TAndroidBluetoothGattServer);
destructor Destroy; override;
procedure onCharacteristicReadRequest(device: JBluetoothDevice; requestId: Integer; offset: Integer; characteristic: JBluetoothGattCharacteristic); cdecl;
procedure onCharacteristicWriteRequest(device: JBluetoothDevice; requestId: Integer; characteristic: JBluetoothGattCharacteristic; preparedWrite: Boolean; responseNeeded: Boolean; offset: Integer; value: TJavaArray); cdecl;
procedure onConnectionStateChange(device: JBluetoothDevice; status: Integer; newState: Integer); cdecl;
procedure onDescriptorReadRequest(device: JBluetoothDevice; requestId: Integer; offset: Integer; descriptor: JBluetoothGattDescriptor); cdecl;
procedure onDescriptorWriteRequest(device: JBluetoothDevice; requestId: Integer; descriptor: JBluetoothGattDescriptor; preparedWrite: Boolean; responseNeeded: Boolean; offset: Integer; value: TJavaArray); cdecl;
procedure onExecuteWrite(device: JBluetoothDevice; requestId: Integer; execute: Boolean); cdecl;
procedure onServiceAdded(AStatus: Integer; service: JBluetoothGattService); cdecl;
end;
TAndroidBluetoothAdvertiseListener = class(TJavaLocal, JRTLAdvertiseListener)
private
FEvent: TEvent;
FerrorCode: Integer;
public
constructor Create(const AEvent: TEvent; AErrorCode: Integer);
destructor Destroy; override;
procedure onStartFailure(errorCode: Integer); cdecl;
procedure onStartSuccess(settingsInEffect: JAdvertiseSettings); cdecl;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TGattConnectionErrorStatus = (
OK = 0, //< Connection/Disconnection OK
TIMEOUT = 8, //< Device went out of range, or connection time-out
TERMINATED_BY_PEER = 19, //< Disconnected by device
ERROR_BONDING_OR_SVC_DISCOVERY = 22, //< Issue with bond or Services Discovery
ERROR_62 = 62, //< Error connecting, device not found
ERROR_133 = 133
);
TAndroidBluetoothGattListener = class(TJavaLocal, JRTLBluetoothGattListener)
private
[weak] FGatt: TAndroidBluetoothLEDevice;
private
class function connectionStatusStringGet(status : Integer) : UnicodeString; static;
class function connectionStateStringGet(state : Integer) : UnicodeString; static;
public
constructor Create(const ALEDevice: TAndroidBluetoothLEDevice);
destructor Destroy; override;
procedure onCharacteristicChanged(gatt: JBluetoothGatt; characteristic: JBluetoothGattCharacteristic); cdecl;
procedure onCharacteristicRead(gatt: JBluetoothGatt; characteristic: JBluetoothGattCharacteristic; status: Integer); cdecl;
procedure onCharacteristicWrite(gatt: JBluetoothGatt; characteristic: JBluetoothGattCharacteristic; status: Integer); cdecl;
procedure onConnectionStateChange(gatt: JBluetoothGatt; status: Integer; newState: Integer); cdecl;
procedure onDescriptorRead(gatt: JBluetoothGatt; descriptor: JBluetoothGattDescriptor; status: Integer); cdecl;
procedure onDescriptorWrite(gatt: JBluetoothGatt; descriptor: JBluetoothGattDescriptor; status: Integer); cdecl;
procedure onReadRemoteRssi(gatt: JBluetoothGatt; rssi: Integer; status: Integer); cdecl;
procedure onReliableWriteCompleted(gatt: JBluetoothGatt; status: Integer); cdecl;
procedure onServicesDiscovered(gatt: JBluetoothGatt; status: Integer); cdecl;
end;
// --------------------------------------------------------------------- //
TAndroidBluetoothLEDevice = class(TBluetoothLEDevice)
public
type
DeviceActivity = (
Connecting,
Disconnecting,
SvcScanning,
ReadingRSSI,
Reading,
Writing,
ReadingDcr,
WritingDcr,
ReliableWriting
);
DeviceActivities = set of DeviceActivity;
class var
FRefreshMethod: Boolean;
class constructor Create;
public
const
c_GattCbkTimeout = 5000;
c_connDelayMax = 1000;
c_connDelay = 200;
c_connCancelDelay = 200;
c_svcCacheRefreshTmo = 500;
private
FJDevice: JBluetoothDevice;
FJGattCallback: JRTLBluetoothGattCallback;
FJGattListener: JRTLBluetoothGattListener;
FJGatt: JBluetoothGatt;
[volatile]
m_lastStatus: TBluetoothGattStatus;
[volatile]
m_connStatus: TBluetoothDeviceState;
[volatile]
m_state : DeviceActivities;
private
procedure internalConnect();
procedure internalNotifyDisconnected();
procedure internalNotifyConnected(withDelay : Boolean = true);
procedure handleConnectionAndBonding();
procedure internalDisconnect();
procedure internalClose();
function internalGetGattClient(): JBluetoothGatt;
procedure servicesDiscovered(status : Integer);
procedure bondingChanged(statePrev, newState : Integer);
procedure unpair();
protected
// From TBluetoothCustomDevice
function DoCreateAdvertiseData() : TBluetoothLEAdvertiseData; override;
function GetAddress() : TBluetoothMacAddress; override;
function GetDeviceName() : string; override;
function GetBluetoothType() : TBluetoothType; override;
function GetIdentifier() : string; override;
function GetIsConnected() : Boolean; override;
function isBusy() : Boolean;
strict private
procedure updateServicesList();
procedure clearServicesCache();
private
function FindCharacteristic(const AJCharacteristic: JBluetoothGattCharacteristic): TAndroidBluetoothGattCharacteristic;
function FindDescriptor(const AJDescriptor: JBluetoothGattDescriptor): TAndroidBluetoothGattDescriptor;
procedure ensureServicesActualized();
protected
procedure DoAbortReliableWrite; override;
function DoBeginReliableWrite: Boolean; override;
function DoExecuteReliableWrite: Boolean; override;
function DoDiscoverServices: Boolean; override;
function DoReadCharacteristic(const ACharacteristic: TBluetoothGattCharacteristic): Boolean; override;
function DoReadDescriptor(const ADescriptor: TBluetoothGattDescriptor): Boolean; override;
function DoWriteCharacteristic(const ACharacteristic: TBluetoothGattCharacteristic): Boolean; override;
function DoWriteDescriptor(const ADescriptor: TBluetoothGattDescriptor): Boolean; override;
function DoReadRemoteRSSI: Boolean; override;
function DoSetCharacteristicNotification(const ACharacteristic: TBluetoothGattCharacteristic; Enable: Boolean): Boolean; override;
function DoDisconnect: Boolean; override;
function DoConnect: Boolean; override;
function DoRequestMtu(AMtu: Integer): Boolean; override;
public
constructor Create(const AJDevice: JBluetoothDevice; AutoConnect: Boolean; AForceRefreshCachedServices: Boolean = False);
destructor Destroy(); override;
class function bondStateStringGet(bondState : Integer) : UnicodeString; static;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothGattService = class(TBluetoothGattService)
private
function FindCharacteristic(AJCharacteristic: JBluetoothGattCharacteristic): TAndroidBluetoothGattCharacteristic;
function FindDescriptor(AJDescriptor: JBluetoothGattDescriptor): TAndroidBluetoothGattDescriptor;
protected
FJService: JBluetoothGattService;
function GetServiceType: TBluetoothServiceType; override;
function GetServiceUUID: TBluetoothUUID; override;
function DoGetCharacteristics: TBluetoothGattCharacteristicList; override;
function DoAddCharacteristic(const ACharacteristic: TBluetoothGattCharacteristic): Boolean; override;
function DoAddIncludedService(const AService: TBluetoothGattService): Boolean; override;
function DoCreateIncludedService(const AnUUID: TBluetoothUUID; AType: TBluetoothServiceType): TBluetoothGattService; override;
function DoCreateCharacteristic(const AUuid: TBluetoothUUID; APropertyFlags: TBluetoothPropertyFlags;
const ADescription: string): TBluetoothGattCharacteristic; override;
function DoGetIncludedServices: TBluetoothGattServiceList; override;
public
constructor Create(const AnUUID: TBluetoothUUID; AType: TBluetoothServiceType; const AJService: JBluetoothGattService);
destructor Destroy; override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothGattCharacteristic = class(TBluetoothGattCharacteristic)
private
function FindDescriptor(const AJDescriptor: JBluetoothGattDescriptor): TAndroidBluetoothGattDescriptor;
protected
FJCharacteristic: JBluetoothGattCharacteristic;
function GetUUID: TBluetoothUUID; override;
function GetProperties: TBluetoothPropertyFlags; override;
function DoAddDescriptor(const ADescriptor: TBluetoothGattDescriptor): Boolean; override;
function DoCreateDescriptor(const AUUID: TBluetoothUUID): TBluetoothGattDescriptor; override;
function DoGetDescriptors: TBluetoothGattDescriptorList; override;
function DoGetValue: TBytes; override;
procedure DoSetValue(const AValue: TBytes); override;
public
constructor Create(const AService: TBluetoothGattService; AJCharacteristic: JBluetoothGattCharacteristic); overload;
constructor Create(const AService: TBluetoothGattService; const AUUID: TBluetoothUUID; AProperties: TBluetoothPropertyFlags); overload;
destructor Destroy; override;
end;
// --------------------------------------------------------------------- //
// --------------------------------------------------------------------- //
TAndroidBluetoothGattDescriptor = class(TBluetoothGattDescriptor)
protected
FJDescriptor: JBluetoothGattDescriptor;
function DoGetValue: TBytes; override;
procedure DoSetValue(const AValue: TBytes); override;
function GetUUID: TBluetoothUUID; override;
// Characteristic Extended Properties
function DoGetReliableWrite: Boolean; override;
function DoGetWritableAuxiliaries: Boolean; override;
// Characteristic User Description
function DoGetUserDescription: string; override;
procedure DoSetUserDescription(const Value: string); override;
// Client Characteristic Configuration
procedure DoSetNotification(const Value: Boolean); override;
function DoGetNotification: Boolean; override;
procedure DoSetIndication(const Value: Boolean); override;
function DoGetIndication: Boolean; override;
// Server Characteristic Configuration
function DoGetBroadcasts: Boolean; override;
procedure DoSetBroadcasts(const Value: Boolean); override;
// Characteristic Presentation Format
function DoGetFormat: TBluetoothGattFormatType; override;
function DoGetExponent: ShortInt; override;
function DoGetFormatUnit: TBluetoothUUID; override;
public
constructor Create(const ACharacteristic: TBluetoothGattCharacteristic; AJDescriptor: JBluetoothGattDescriptor);
destructor Destroy; override;
end;
// --------------------------------------------------------------------- //
// Helper fuctions
// --------------------------------------------------------------------- //
const
PROPERTY_BROADCAST: Integer = $0001;
PROPERTY_READ: Integer = $0002;
PROPERTY_WRITE_NO_RESPONSE: Integer = $0004;
PROPERTY_WRITE: Integer = $0008;
PROPERTY_NOTIFY: Integer = $0010;
PROPERTY_INDICATE: Integer = $0020;
PROPERTY_SIGNED_WRITE: Integer = $0040;
PROPERTY_EXTENDED_PROPS: Integer = $0080;
REQUEST_DISCOVERABLE = 2001;
REQUEST_ENABLE_BT = 2002;
CHAR_EXTENDEDPROPERTIES: TBluetoothUUID = '{00002900-0000-1000-8000-00805F9B34FB}';
CHAR_DESCRIPTION: TBluetoothUUID = '{00002901-0000-1000-8000-00805F9B34FB}';
CHAR_CLIENT_CONFIG: TBluetoothUUID = '{00002902-0000-1000-8000-00805F9B34FB}';
CHAR_SERVERCONFIGURATIONFORMAT: TBluetoothUUID = '{00002903-0000-1000-8000-00805F9B34FB}';
function PropertiesToInteger(const AProps: TBluetoothPropertyFlags): Integer;
begin
Result := 0;
if TBluetoothProperty.Broadcast in AProps then
Result := Result or PROPERTY_BROADCAST;
if TBluetoothProperty.Read in AProps then
Result := Result or PROPERTY_READ;
if TBluetoothProperty.WriteNoResponse in AProps then
Result := Result or PROPERTY_WRITE_NO_RESPONSE;
if TBluetoothProperty.Write in AProps then
Result := Result or PROPERTY_WRITE;
if TBluetoothProperty.Notify in AProps then
Result := Result or PROPERTY_NOTIFY;
if TBluetoothProperty.Indicate in AProps then
Result := Result or PROPERTY_INDICATE;
if TBluetoothProperty.SignedWrite in AProps then
Result := Result or PROPERTY_SIGNED_WRITE;
if TBluetoothProperty.ExtendedProps in AProps then
Result := Result or PROPERTY_EXTENDED_PROPS;
end;
function CheckOSVersionForGattClient: Boolean;
begin
Result := TOSVersion.Check(
4,
3);
end;
function CheckOSVersionForGattServer: Boolean;
begin
Result := TOSVersion.Check(5);
end;
// --------------------------------------------------------------------- //
{ TBluetoothBroadcastReceiverListener }
constructor TBluetoothBroadcastListener.Create(const AnAdapter: TAndroidBluetoothAdapter);
begin
inherited Create;
FAdapter := AnAdapter;
end;
procedure TBluetoothBroadcastListener.onReceive(context: JContext; intent: JIntent);
var
LStrAction: string;
LJBluetoothDevice: JBluetoothDevice;
LDevice: TBluetoothDevice;
LBTDevice: TAndroidBluetoothDevice;
begin
LStrAction := JStringToString(intent.getAction);
if LStrAction = JStringToString(TJBluetoothAdapter.JavaClass.ACTION_STATE_CHANGED) then
begin
if Assigned(FAdapter) then
FAdapter.FRequestEnableCallback := False;
end
else if LStrAction = JStringToString(TJBluetoothDevice.JavaClass.ACTION_FOUND) then
begin
LJBluetoothDevice := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
LBTDevice := TAndroidBluetoothDevice.Create(LJBluetoothDevice);
TAndroidBluetoothManager.AddDeviceToList(
LBTDevice,
TAndroidBluetoothManager(FAdapter.FManager).FDiscoveredDevices);
end
else if LStrAction = JStringToString(TJBluetoothAdapter.JavaClass.ACTION_DISCOVERY_STARTED) then
begin
end
else if LStrAction = JStringToString(TJBluetoothAdapter.JavaClass.ACTION_DISCOVERY_FINISHED) then
begin
if (FAdapter.FDiscoveryThreadTimer <> nil) and FAdapter.FDiscoveryThreadTimer.FDiscovering and
not FAdapter.FDiscoveryCancelled then
if not FAdapter.FJAdapter.startDiscovery then
FAdapter.FDiscoveryThreadTimer.FEvent.SetEvent;
end
else if LStrAction = JStringToString(TJBluetoothDevice.JavaClass.ACTION_BOND_STATE_CHANGED) then
begin
// Broadcast Action: Indicates a change in the bond state of a remote device. For example, if a device is bonded (paired).
// Always contains the extra fields EXTRA_DEVICE, EXTRA_BOND_STATE and EXTRA_PREVIOUS_BOND_STATE.
LJBluetoothDevice := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
end
else if LStrAction = JStringToString(TJBluetoothDevice.JavaClass.ACTION_ACL_CONNECTED) then
begin
LJBluetoothDevice := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
end
else if LStrAction = JStringToString(TJBluetoothDevice.JavaClass.ACTION_ACL_DISCONNECTED) then
begin
LJBluetoothDevice := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
end
else if LStrAction = JStringToString(TJBluetoothAdapter.JavaClass.ACTION_SCAN_MODE_CHANGED) then
begin
if FAdapter.FOldScanMode = TJBluetoothAdapter.JavaClass.SCAN_MODE_CONNECTABLE_DISCOVERABLE then
FAdapter.DoDiscoverableEnd(FAdapter);
FAdapter.FOldScanMode := intent.getIntExtra(
TJBluetoothAdapter.JavaClass.EXTRA_SCAN_MODE,
TJBluetoothAdapter.JavaClass.ERROR)
end
else if TOSVersion.Check(4, 0, 3) then // API Level 15
begin
if LStrAction = JStringToString(TJBluetoothDevice.JavaClass.ACTION_UUID) then
begin
LJBluetoothDevice := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
for LDevice in TAndroidBluetoothManager(TBlueToothManager.Current).FPairedDevices do
begin
if TAndroidBluetoothDevice(LDevice).FJDevice.equals(LJBluetoothDevice) then
begin
LBTDevice := TAndroidBluetoothDevice(LDevice);
LBTDevice.FEventUUIDS.SetEvent;
break;
end;
end;
end;
// API Level 19
if TOSVersion.Check(4, 4) then
begin
if LStrAction = JStringToString(TJBluetoothDevice.JavaClass.ACTION_PAIRING_REQUEST) then
begin
// EXTRA_DEVICE
LJBluetoothDevice := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
LBTDevice := TAndroidBluetoothDevice.Create(LJBluetoothDevice);
FAdapter.DoRemoteRequestPair(LBTDevice);
end
end;
end;
end;
{ TPlatformBluetoothClassicManager }
class function TPlatformBluetoothClassicManager.GetBluetoothManager: TBluetoothManager;
begin
Result := TAndroidBluetoothManager.Create;
end;
{ TPlatformBluetoothLEManager }
class function TPlatformBluetoothLEManager.GetBluetoothManager: TBluetoothLEManager;
begin
Result := TAndroidBluetoothLEManager.Create;
end;
{ TAndroidBluetoothAdapter }
procedure TAndroidBluetoothAdapter.DoCancelDiscovery;
begin
FDiscoveryCancelled := True;
if (FDiscoveryThreadTimer <> nil) then
FDiscoveryThreadTimer.FEvent.SetEvent;
FJAdapter.cancelDiscovery;
FDiscoveryThreadTimer.Free;
end;
constructor TAndroidBluetoothAdapter.Create(const AManager: TBluetoothManager; const AJAdapter: JBluetoothAdapter);
begin
inherited Create(AManager);
// Do the magic
FJAdapter := AJAdapter;
FContext := TAndroidHelper.Context;
FBroadcastListener := TBluetoothBroadcastListener.Create(Self);
FBroadcastReceiver := TJFMXBroadcastReceiver.JavaClass.init(FBroadcastListener);
FIntentFilter := TJIntentFilter.JavaClass.init(TJBluetoothDevice.JavaClass.ACTION_FOUND);
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_BOND_STATE_CHANGED);
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_ACL_CONNECTED);
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_ACL_DISCONNECTED);
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_ACL_DISCONNECT_REQUESTED);
FIntentFilter.addAction(TJBluetoothAdapter.JavaClass.ACTION_DISCOVERY_STARTED);
FIntentFilter.addAction(TJBluetoothAdapter.JavaClass.ACTION_DISCOVERY_FINISHED);
FIntentFilter.addAction(TJBluetoothAdapter.JavaClass.ACTION_SCAN_MODE_CHANGED);
FIntentFilter.addAction(TJBluetoothAdapter.JavaClass.ACTION_STATE_CHANGED);
// API Level 15
if TOSVersion.Check(4, 0, 3) then
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_UUID);
// API Level 19
if TOSVersion.Check(4, 4) then
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_PAIRING_REQUEST);
FContext.registerReceiver(
FBroadcastReceiver,
FIntentFilter);
TMessageManager.DefaultManager.SubscribeToMessage(
TMessageResultNotification,
ResultCallback);
FRequestEnableCallback := False;
FOldScanMode := TJBluetoothAdapter.JavaClass.ERROR;
end;
destructor TAndroidBluetoothAdapter.Destroy;
begin
TMessageManager.DefaultManager.Unsubscribe(
TMessageResultNotification,
ResultCallback
);
// First cancel any pending discovery.
FJAdapter.cancelDiscovery;
// Unregister Receiver.
FContext.unregisterReceiver(FBroadcastReceiver);
FBroadcastReceiver := nil;
FBroadcastListener.Free;
FIntentFilter := nil;
FJAdapter := nil;
FContext := nil;
inherited;
end;
function TAndroidBluetoothAdapter.DoCreateServerSocket(const AName: string; const AUUID: TGUID;
Secure: Boolean): TBluetoothServerSocket;
var
LJServerSocket: JBluetoothServerSocket;
LJUUID: JUUID;
begin
LJUUID := BluetoothUuidToJUuid(AUUID);
if Secure then
LJServerSocket := FJAdapter.listenUsingRfcommWithServiceRecord(
StringToJString(AName),
LJUUID)
else
LJServerSocket := FJAdapter.listenUsingInsecureRfcommWithServiceRecord(
StringToJString(AName),
LJUUID);
Result := TAndroidBluetoothServerSocket.Create(LJServerSocket);
end;
function TAndroidBluetoothAdapter.GetAdapterName: string;
begin
Result := JStringToString(FJAdapter.getName);
end;
function TAndroidBluetoothAdapter.GetAddress: TBluetoothMacAddress;
begin
Result := JStringToString(FJAdapter.getAddress);
end;
function TAndroidBluetoothAdapter.GetPairedDevices: TBluetoothDeviceList;
var
LSetDevices: JSet;
LIterator: JIterator;
LDevice: JBluetoothDevice;
begin
TAndroidBluetoothManager(FManager).FPairedDevices.Clear;
// Retrieve Paired Devices.
LSetDevices := FJAdapter.getBondedDevices;
LIterator := LSetDevices.iterator;
if not LSetDevices.isEmpty then
while LIterator.hasNext do
begin
LDevice := TJBluetoothDevice.Wrap(LIterator.next);
TAndroidBluetoothManager(FManager).FPairedDevices.Add(TAndroidBluetoothDevice.Create(LDevice));
end;
Result := TAndroidBluetoothManager(FManager).FPairedDevices;
end;
function TAndroidBluetoothAdapter.GetRemoteDevice(const AnAddress: TBluetoothMacAddress): TBluetoothDevice;
begin
Result := TAndroidBluetoothDevice.Create(FJAdapter.getRemoteDevice(StringToJString(AnAddress)));
end;
function TAndroidBluetoothAdapter.GetScanMode: TBluetoothScanMode;
var
LScanMode: Integer;
begin
LScanMode := FJAdapter.getScanMode;
case LScanMode of
20:
Result := TBluetoothScanMode.None;
21:
Result := TBluetoothScanMode.Connectable;
23:
Result := TBluetoothScanMode.Discoverable;
else
raise EBluetoothAdapterException.Create(SBluetoothScanModeError);
end;
end;
function TAndroidBluetoothAdapter.GetState: TBluetoothAdapterState;
begin
case FJAdapter.getState of
// STATE_OFF, STATE_TURNING_ON, STATE_TURNING_OFF.
10, 11, 13:
Result := TBluetoothAdapterState.Off;
// STATE_ON
12:
begin
if FJAdapter.isDiscovering then
Result := TBluetoothAdapterState.Discovering
else
Result := TBluetoothAdapterState.&On;
end;
else
raise EBluetoothAdapterException.Create(SBluetoothStateError);
end;
end;
function TAndroidBluetoothAdapter.DoPair(const ADevice: TBluetoothDevice): Boolean;
begin
// API Level 19
if TOSVersion.Check(4, 4) then
Result := TAndroidBluetoothDevice(ADevice).FJDevice.createBond
else
Result := False;
end;
procedure TAndroidBluetoothAdapter.SetAdapterName(const Value: string);
begin
inherited;
FJAdapter.setName(StringToJString(Value));
end;
procedure TAndroidBluetoothAdapter.DoStartDiscoverable(Timeout: Cardinal);
var
LIntent: JIntent;
begin
inherited;
if FJAdapter.isEnabled and (FJAdapter.getScanMode <> TJBluetoothAdapter.JavaClass.SCAN_MODE_CONNECTABLE_DISCOVERABLE) then
begin
FWaitingCallBack := true;
LIntent := TJIntent.JavaClass.init(TJBluetoothAdapter.JavaClass.ACTION_REQUEST_DISCOVERABLE);
LIntent.putExtra(
TJBluetoothAdapter.JavaClass.EXTRA_DISCOVERABLE_DURATION,
Integer(Timeout));
TAndroidHelper.Activity.startActivityForResult(
LIntent,
REQUEST_DISCOVERABLE);
{ Wait here until adapter becomes discoverable. It happens before the user answers system dialog about permissions }
InternalProcessMessages;
while FWaitingCallBack and (FJAdapter.getScanMode <> TJBluetoothAdapter.JavaClass.SCAN_MODE_CONNECTABLE_DISCOVERABLE) do
InternalProcessMessages;
end;
end;
procedure TAndroidBluetoothAdapter.ResultCallback(const Sender: TObject; const M: TMessage);
var
LMessage: TMessageResultNotification;
begin
LMessage := TMessageResultNotification(M);
case LMessage.RequestCode of
REQUEST_DISCOVERABLE:
begin
// { Now we can use LMessage.RequestCode, LMessage.ResultCode and LMessage.Value (JIntent object) }
// if LMessage.ResultCode <> 0 then
// begin
// { The user has accepted the Discoverable state. When it finishes show }
// end;
FWaitingCallback := False;
end;
REQUEST_ENABLE_BT:
FRequestEnableCallback := False;
end;
end;
procedure TAndroidBluetoothAdapter.DoStartDiscovery(Timeout: Cardinal);
begin
if
FJAdapter.isEnabled and
not FJAdapter.isDiscovering
then begin
{ Under Android you cannont Set the Timeout value for the Discovery }
{ Launch the Discovery }
if FJAdapter.startDiscovery then
begin
TAndroidBluetoothManager(FManager).FDiscoveredDevices.Clear;
// Start Timer for Discovery process.
FDiscoveryCancelled := False;
if Assigned(FDiscoveryThreadTimer) then
FDiscoveryThreadTimer.DisposeOf;
FDiscoveryThreadTimer := TThreadTimer.Create(
Self,
Self.DoDiscoveryEnd,
Timeout
);
FDiscoveryThreadTimer.Start;
// Ensure that wer are discovering before returning.
// while not FJAdapter.isDiscovering do
// Sleep(1);
end;
end;
end;
function TAndroidBluetoothAdapter.DoUnPair(const ADevice: TBluetoothDevice): Boolean;
begin
{ Cannot Unpair devices in Android. }
raise EBluetoothAdapterException.CreateFmt(SBluetoothNotSupported, ['UnPair', 'Android']); // Do not translate.
end;
{ TAndroidBluetoothAdapter.TThreadTimer }
procedure TAndroidBluetoothAdapter.TThreadTimer.Cancel;
begin
Terminate;
FDiscovering := False;
FEvent.SetEvent;
if Assigned(FOnTimer) then
FOnTimer := nil;
end;
constructor TAndroidBluetoothAdapter.TThreadTimer.Create(const AnAdapter: TBluetoothAdapter; const AEvent: TDiscoveryEndEvent; Timeout: Cardinal);
begin
inherited Create(True);
FAdapter := AnAdapter;
FOnTimer := AEvent;
FTimeout := Timeout;
FEvent := TEvent.Create;
end;
destructor TAndroidBluetoothAdapter.TThreadTimer.Destroy;
begin
Cancel;
inherited;
FEvent.Free;
end;
procedure TAndroidBluetoothAdapter.TThreadTimer.Execute;
var
LWaitResult: TWaitResult;
begin
inherited;
FDiscovering := True;
LWaitResult := FEvent.WaitFor(FTimeout);
if LWaitResult = TWaitResult.wrTimeout then
begin
FDiscovering := False;
end
else
if LWaitResult = TWaitResult.wrSignaled then
begin
// There has been a cancelation of the discover process.
if TAndroidBluetoothAdapter(FAdapter).FDiscoveryCancelled then
Exit;
TAndroidBluetoothAdapter(FAdapter).FJAdapter.cancelDiscovery;
end;
TAndroidBluetoothAdapter(FAdapter).FJAdapter.cancelDiscovery;
if not Terminated and not TAndroidBluetoothAdapter(FAdapter).FDiscoveryCancelled and Assigned(FOnTimer) then
begin
try
FOnTimer(
FAdapter,
TAndroidBluetoothManager(TAndroidBluetoothAdapter(FAdapter).FManager).FDiscoveredDevices);
except
if Assigned(System.Classes.ApplicationHandleException) then
System.Classes.ApplicationHandleException(Self)
else
raise;
end;
end;
end;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
constructor TBluetoothLEBroadcastListener.Create(const adapter: TAndroidBluetoothLEAdapter);
begin
inherited Create;
m_adapter := adapter;
end;
procedure TBluetoothLEBroadcastListener.onReceive(context: JContext; intent: JIntent);
begin
var actionStr := JStringToString(intent.getAction);
if actionStr = JStringToString(TJBluetoothDevice.JavaClass.ACTION_BOND_STATE_CHANGED) then
begin
var jdev := TJBluetoothDevice.Wrap(intent.getParcelableExtra(TJBluetoothDevice.JavaClass.EXTRA_DEVICE));
Assert(Assigned(jdev));
var addr := JStringToString( jdev.getAddress() );
for var dev in m_adapter.FManager.LastDiscoveredDevices do
begin
if dev.Address = addr then
begin
var newState := intent.getIntExtra(
TJBluetoothDevice.JavaClass.EXTRA_BOND_STATE,
TJBluetoothDevice.JavaClass.ERROR
);
var prevState := intent.getIntExtra(
TJBluetoothDevice.JavaClass.EXTRA_PREVIOUS_BOND_STATE,
-1
);
(dev as TAndroidBluetoothLEDevice).bondingChanged(
prevState,
newState
);
break;
end;
end;
end;
end;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
{ TAndroidBluetoothLEAdapter }
constructor TAndroidBluetoothLEAdapter.Create(const AManager: TBluetoothLEManager; const AJAdapter: JBluetoothAdapter);
begin
inherited Create(AMAnager);
m_state := [];
FJAdapter := AJAdapter;
FContext := TAndroidBluetoothLEManager(AManager).FContext;
if TOSVersion.Check(4, 3) then
if TOSVersion.Check(5) then
begin
FJScanListener := TScanCallback.Create(Self);
FJScanCallback := TJRTLScanCallback.JavaClass.init(FJScanListener);
end else
FLeCallback := TBluetoothLeScanCallback.Create(Self);
TMessageManager.DefaultManager.SubscribeToMessage(
TMessageResultNotification,
ResultCallback
);
FRequestEnableCallback := False;
FBroadcastListener := TBluetoothLEBroadcastListener.Create(Self);
FBroadcastReceiver := TJFMXBroadcastReceiver.JavaClass.init(FBroadcastListener);
FIntentFilter := TJIntentFilter.JavaClass.init(TJBluetoothDevice.JavaClass.ACTION_FOUND);
FIntentFilter.addAction(TJBluetoothDevice.JavaClass.ACTION_BOND_STATE_CHANGED);
FContext.registerReceiver(
FBroadcastReceiver,
FIntentFilter
);
end;
destructor TAndroidBluetoothLEAdapter.Destroy;
begin
TMessageManager.DefaultManager.Unsubscribe(
TMessageResultNotification,
ResultCallback
);
FContext.unregisterReceiver(FBroadcastReceiver);
FBroadcastReceiver := nil;
FreeAndNil(FBroadcastListener);
FIntentFilter := nil;
FLeCallback := nil;
FJScanCallback := nil;
FJScanListener := nil;
FJAdapter := nil;
FContext := nil;
FTimerThread.Free;
inherited;
end;
procedure TAndroidBluetoothLEAdapter.DoCancelDiscovery;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEAdapter.DoCancelDiscovery');
StopScan;
FreeAndNil(FTimerThread);
inherited;
end;
procedure TAndroidBluetoothLEAdapter.StopScan;
begin
ES_BTAPI_TRACE(
'TAndroidBluetoothLEAdapter.StopScan; isScanning: %s',
[
BoolToStr(
isScanning,
true
)
]
);
if not isScanning then
Exit();
if TOSVersion.Check(5) then
begin
if (FJScanner <> nil) then
FJScanner.stopScan(FJScanCallback)
end else
FJAdapter.stopLeScan(FLeCallback);
Exclude(
m_state,
Activity.Scanning
);
end;
procedure TAndroidBluetoothLEAdapter.DoDiscoveryEnd(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList);
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEAdapter.DoDiscoveryEnd');
StopScan;
inherited DoDiscoveryEnd(Sender, ADeviceList);
end;
function TAndroidBluetoothLEAdapter.DoStartDiscovery(Timeout: Cardinal; const FilterUUIDList: TBluetoothUUIDsList;
const ascanFilterList: TBluetoothLEScanFilterList): Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEAdapter.DoStartDiscovery');
if
isScanning or
(Activity.InitiatingScan in m_state)
then
Exit(true);
Include(
m_state,
Activity.InitiatingScan
);
m_scanFilterList := nil;
var LServiceUUIDs := BluetoothUUIDsListToJavaArrayUUID(FilterUUIDList);
if TOSVersion.Check(5) then
begin
var filterList := ascanFilterList;
if
Assigned(FilterUUIDList) and
(FilterUUIDList.Count > 0)
then begin
filterList := TBluetoothLEScanFilterList.Create;
for var I := 0 to FilterUUIDList.Count - 1 do
begin
filterList.Add(TBluetoothLEScanFilter.Create);
filterList[I].ServiceUUID := FilterUUIDList[I];
end;
end;
Result := DoStartDiscoveryRaw(filterList);
end else if Assigned(LServiceUUIDs) then
begin
Result := FJAdapter.startLeScan(
LServiceUUIDs,
FLeCallback
);
end else begin
m_scanFilterList := ascanFilterList;
Result := FJAdapter.startLeScan(FLeCallback);
end;
if Result then
begin
FTimerThread := TThreadTimer.Create(
Self,
Self.DoDiscoveryEnd,
Timeout
);
FTimerThread.Start;
end;
Exclude(
m_state,
Activity.InitiatingScan
);
end;
function TAndroidBluetoothLEAdapter.DoStartDiscoveryRaw(const ascanFilterList: TBluetoothLEScanFilterList; Refresh: Boolean): Boolean;
function CreateScanFilters: JArrayList;
const
MANUFACTURER_IDMASK: Word = $0101;
BEACON_MANUFACTURER_ID_POSITION = 0;
var
LJScanFilter_Builder: JScanFilter_Builder;
LManufacturerSpecificData: TBytes;
LManufacturerSpecificDataMask: TBytes;
LManufacturerId: Word;
I: Integer;
LHardwareFilter: Boolean;
begin
LHardwareFilter := True;
Result := TJArrayList.JavaClass.init;
for I := 0 to ascanFilterList.Count - 1 do
begin
if ascanFilterList[I].ServiceUUID <> TBluetoothUUID.Empty then
begin
LJScanFilter_Builder := TJScanFilter_Builder.JavaClass.init;
if ascanFilterList[I].ServiceUUIDMask <> TBluetoothUUID.Empty then
LJScanFilter_Builder.setServiceUuid(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceUUID)),
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceUUIDMask)))
else
LJScanFilter_Builder.setServiceUuid(TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceUUID)));
if ascanFilterList[I].ServiceData.Key <> TBluetoothUUID.Empty then
begin
if (ascanFilterList[I].ServiceDataMask <> nil) and (ascanFilterList[I].ServiceData.Value <> nil) and
(Length(ascanFilterList[I].ServiceDataMask) = Length(ascanFilterList[I].ServiceData.Value)) then
LJScanFilter_Builder.setServiceData(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceData.Key)),
TBytesToTJavaArray(ascanFilterList[I].ServiceData.Value),
TBytesToTJavaArray(ascanFilterList[I].ServiceDataMask))
else
LJScanFilter_Builder.setServiceData(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceData.Key)),
TBytesToTJavaArray(ascanFilterList[I].ServiceData.Value));
ascanFilterList[I].ServiceData := TServiceDataRawData.Create(
TBluetoothUUID.Empty,
nil);
end
else
begin
if (ascanFilterList[I].ManufacturerSpecificData <> nil) and
(Length(ascanFilterList[I].ManufacturerSpecificData) > 1) then
begin
LManufacturerId := PWord(@ascanFilterList[I].ManufacturerSpecificData[0])^;
SetLength(
LManufacturerSpecificData,
Length(ascanFilterList[I].ManufacturerSpecificData) -
LManufacturerId.Size);
Move(
ascanFilterList[I].ManufacturerSpecificData[LManufacturerId.Size],
LManufacturerSpecificData[0],
Length(LManufacturerSpecificData));
if (ascanFilterList[I].ManufacturerSpecificDataMask <> nil) and
(Length(ascanFilterList[I].ManufacturerSpecificData) =
Length(ascanFilterList[I].ManufacturerSpecificDataMask)) then
begin
SetLength(
LManufacturerSpecificDataMask,
Length(LManufacturerSpecificData));
Move(
ascanFilterList[I].ManufacturerSpecificDataMask[LManufacturerId.Size],
LManufacturerSpecificDataMask[0],
Length(LManufacturerSpecificDataMask));
LJScanFilter_Builder.setManufacturerData(
LManufacturerId,
TBytesToTJavaArray(LManufacturerSpecificData),
TBytesToTJavaArray(LManufacturerSpecificDataMask));
if PWord(@ascanFilterList[I].ManufacturerSpecificDataMask[BEACON_MANUFACTURER_ID_POSITION])^ <
MANUFACTURER_IDMASK then
LHardwareFilter := False
end
else
LJScanFilter_Builder.setManufacturerData(
LManufacturerId,
TBytesToTJavaArray(LManufacturerSpecificData));
ascanFilterList[I].ManufacturerSpecificData := nil;
end;
end;
Result.add(LJScanFilter_Builder.build);
end;
if (ascanFilterList[I].ManufacturerSpecificData <> nil) and
(Length(ascanFilterList[I].ManufacturerSpecificData) > 1) then
begin
LJScanFilter_Builder := TJScanFilter_Builder.JavaClass.init;
LManufacturerId := PWord(@ascanFilterList[I].ManufacturerSpecificData[0])^;
SetLength(
LManufacturerSpecificData,
Length(ascanFilterList[I].ManufacturerSpecificData) - LManufacturerId.Size);
Move(
ascanFilterList[I].ManufacturerSpecificData[LManufacturerId.Size],
LManufacturerSpecificData[0],
Length(LManufacturerSpecificData));
if (ascanFilterList[I].ManufacturerSpecificDataMask <> nil) and
(Length(ascanFilterList[I].ManufacturerSpecificData) =
Length(ascanFilterList[I].ManufacturerSpecificDataMask)) then
begin
SetLength(
LManufacturerSpecificDataMask,
Length(LManufacturerSpecificData));
Move(
ascanFilterList[I].ManufacturerSpecificDataMask[LManufacturerId.Size],
LManufacturerSpecificDataMask[0],
Length(LManufacturerSpecificDataMask));
LJScanFilter_Builder.setManufacturerData(
LManufacturerId,
TBytesToTJavaArray(LManufacturerSpecificData),
TBytesToTJavaArray(LManufacturerSpecificDataMask));
if PWord(@ascanFilterList[I].ManufacturerSpecificDataMask[BEACON_MANUFACTURER_ID_POSITION])^ <
MANUFACTURER_IDMASK then
LHardwareFilter := False
end
else
LJScanFilter_Builder.setManufacturerData(
LManufacturerId,
TBytesToTJavaArray(LManufacturerSpecificData));
Result.add(LJScanFilter_Builder.build);
end;
if ascanFilterList[I].ServiceData.Key <> TBluetoothUUID.Empty then
begin
LJScanFilter_Builder := TJScanFilter_Builder.JavaClass.init;
if (ascanFilterList[I].ServiceDataMask <> nil) and (ascanFilterList[I].ServiceData.Value <> nil) and
(Length(ascanFilterList[I].ServiceDataMask) = Length(ascanFilterList[I].ServiceData.Value)) then
LJScanFilter_Builder.setServiceData(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceData.Key)),
TBytesToTJavaArray(ascanFilterList[I].ServiceData.Value),
TBytesToTJavaArray(ascanFilterList[I].ServiceDataMask))
else
LJScanFilter_Builder.setServiceData(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(ascanFilterList[I].ServiceData.Key)),
TBytesToTJavaArray(ascanFilterList[I].ServiceData.Value));
Result.add(LJScanFilter_Builder.build);
end;
if ascanFilterList[I].LocalName <> '' then
begin
LJScanFilter_Builder := TJScanFilter_Builder.JavaClass.init;
LJScanFilter_Builder.setDeviceName(StringToJString(ascanFilterList[I].LocalName));
Result.add(LJScanFilter_Builder.build);
end;
if ascanFilterList[I].DeviceAddress <> '' then
begin
LJScanFilter_Builder := TJScanFilter_Builder.JavaClass.init;
LJScanFilter_Builder.setDeviceAddress(StringToJString(ascanFilterList[I].DeviceAddress));
Result.add(LJScanFilter_Builder.build);
end;
end;
if not LHardwareFilter then
Result := nil;
end;
var
LJScanSettings_Builder: JScanSettings_Builder;
LScanSettings: Integer;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEAdapter.DoStartDiscoveryRaw');
DoCancelDiscovery;
if TOSVersion.Check(5) then
begin
LScanSettings := TAndroidBluetoothLEManager(FManager).ScanSettings;
if (FJScanSettings = nil) or (FScanSettings <> LScanSettings) then
begin
FScanSettings := LScanSettings;
LJScanSettings_Builder := TJScanSettings_Builder.JavaClass.init;
LJScanSettings_Builder.setScanMode(FScanSettings);
FJScanSettings := LJScanSettings_Builder.build;
end;
if FJScanner = nil then
FJScanner := FJAdapter.getBluetoothLeScanner;
if Assigned(ascanFilterList) then
begin
if
Refresh or
(FJArrayListOfAdvertiseData = nil)
then
FJArrayListOfAdvertiseData := CreateScanFilters;
if Assigned(FJArrayListOfAdvertiseData) then
begin
m_scanFilterList := nil;
Include(
m_state,
Activity.Scanning
);
FJScanner.startScan(
TJList.Wrap(FJArrayListOfAdvertiseData),
FJScanSettings,
FJScanCallback
);
end else begin
m_scanFilterList := ascanFilterList;
Include(
m_state,
Activity.Scanning
);
FJScanner.startScan(
nil,
FJScanSettings,
FJScanCallback
);
end;
end else begin
Include(
m_state,
Activity.Scanning
);
FJScanner.startScan(
nil,
FJScanSettings,
FJScanCallback
);
end;
end else begin
m_scanFilterList := ascanFilterList;
Include(
m_state,
Activity.Scanning
);
Result := FJAdapter.startLeScan(FLeCallback);
if not Result then
Exclude(
m_state,
Activity.Scanning
);
end;
Result := isScanning;
end;
function TAndroidBluetoothLEAdapter.GetAdapterName: string;
begin
Result := JStringToString(FJAdapter.getName);
end;
function TAndroidBluetoothLEAdapter.GetAddress: TBluetoothMacAddress;
begin
Result := JStringToString(FJAdapter.getAddress);
end;
function TAndroidBluetoothLEAdapter.GetRemoteDevice(const AnAddress: TBluetoothMacAddress): TBluetoothLEDevice;
begin
Result := TAndroidBluetoothLEDevice.Create(
FJAdapter.getRemoteDevice(StringToJString(AnAddress)),
True
);
end;
function TAndroidBluetoothLEAdapter.GetScanMode: TBluetoothScanMode;
var
LScanMode: Integer;
begin
LScanMode := FJAdapter.getScanMode;
case LScanMode of
20:
Result := TBluetoothScanMode.None;
21:
Result := TBluetoothScanMode.Connectable;
23:
Result := TBluetoothScanMode.Discoverable;
else
raise EBluetoothLEAdapterException.Create(SBluetoothScanModeError);
end;
end;
function TAndroidBluetoothLEAdapter.GetState: TBluetoothAdapterState;
begin
case FJAdapter.getState of
// STATE_OFF, STATE_TURNING_ON, STATE_TURNING_OFF.
10, 11, 13:
Result := TBluetoothAdapterState.Off;
// STATE_ON
12:
begin
// if FBLEDiscovering then
if isScanning then
Result := TBluetoothAdapterState.Discovering
else
Result := TBluetoothAdapterState.&On;
end;
else
raise EBluetoothLEAdapterException.Create(SBluetoothStateError);
end;
end;
procedure TAndroidBluetoothLEAdapter.SetAdapterName(const Value: string);
begin
inherited;
FJAdapter.setName(StringToJString(Value));
end;
function TAndroidBluetoothLEAdapter.isScanning() : Boolean;
begin
Result := (Activity.Scanning in m_state);
end;
procedure TAndroidBluetoothLEAdapter.ResultCallback(const Sender: TObject; const M: TMessage);
begin
if TMessageResultNotification(M).RequestCode = REQUEST_ENABLE_BT then
FRequestEnableCallback := False;
end;
{ TAndroidBluetoothLEAdapter.TThreadTimer }
constructor TAndroidBluetoothLEAdapter.TThreadTimer.Create(const AnAdapter: TBluetoothLEAdapter;
const AEvent: TDiscoveryLEEndEvent; Timeout: Cardinal);
begin
inherited Create(True);
FAdapter := AnAdapter;
FOnTimer := AEvent;
FTimeout := Timeout;
FEvent := TEvent.Create;
end;
destructor TAndroidBluetoothLEAdapter.TThreadTimer.Destroy;
begin
Terminate;
FEvent.SetEvent;
inherited;
FEvent.Free;
FOnTimer := nil;
end;
procedure TAndroidBluetoothLEAdapter.TThreadTimer.Execute;
begin
inherited;
// var leAdapter := (FAdapter as TAndroidBluetoothLEAdapter);
// Assert(Assigned(leAdapter));
//
// while
// not Terminated and
// not leAdapter.FJAdapter.isDiscovering
// do
// Sleep(1);
//
// leAdapter.m_scanning := leAdapter.FJAdapter.isDiscovering;
FEvent.WaitFor(FTimeout);
if
not Terminated and
Assigned(FOnTimer)
then begin
try
FOnTimer(
FAdapter,
nil
);
except
if Assigned(System.Classes.ApplicationHandleException) then
System.Classes.ApplicationHandleException(Self)
else
raise;
end;
end;
end;
{ TAndroidBluetoothManager }
constructor TAndroidBluetoothManager.Create;
var
LocalManager: JObject;
begin
inherited;
{ This code needs API_18 }
if TOsVersion.Check(4, 3) then
begin
FContext := TAndroidHelper.Context;
LocalManager := FContext.getSystemService(TJContext.JavaClass.BLUETOOTH_SERVICE);
if LocalManager <> nil then
FJManager := TJBluetoothManager.Wrap(LocalManager);
end;
end;
destructor TAndroidBluetoothManager.Destroy;
begin
FJManager := nil;
FContext := nil;
inherited;
end;
function TAndroidBluetoothManager.GetAdapterState: TBluetoothAdapterState;
begin
if FAdapter = nil then
begin
{ API Level 18; }
if TOSVersion.Check(4, 3) then
FAdapter := TAndroidBluetoothAdapter.Create(
Self,
FJManager.getAdapter)
else
FAdapter := TAndroidBluetoothAdapter.Create(
Self,
TJBluetoothAdapter.JavaClass.getDefaultAdapter);
end;
Result := FAdapter.State;
end;
function TAndroidBluetoothManager.DoGetClassicAdapter: TBluetoothAdapter;
begin
if GetAdapterState = TBluetoothAdapterState.Off then
FreeAndNil(FAdapter);
Result := FAdapter;
end;
function TAndroidBluetoothManager.GetConnectionState: TBluetoothConnectionState;
begin
if GetAdapterState = TBluetoothAdapterState.Off then
Result := TBluetoothConnectionState.Disconnected
else
Result := TBluetoothConnectionState.Connected;
end;
function TAndroidBluetoothManager.DoEnableBluetooth: Boolean;
var
LIntent: JIntent;
begin
if GetConnectionState = TBluetoothConnectionState.Disconnected then
begin
TAndroidBluetoothAdapter(FAdapter).FRequestEnableCallback := True;
TThread.CreateAnonymousThread(
procedure
begin
LIntent := TJIntent.JavaClass.init(TJBluetoothAdapter.JavaClass.ACTION_REQUEST_ENABLE);
TAndroidHelper.Activity.startActivityForResult(
LIntent,
REQUEST_ENABLE_BT);
end
).Start;
end;
Result := True;
end;
{ TAndroidBluetoothDevice }
constructor TAndroidBluetoothDevice.Create(const AJDevice: JBluetoothDevice);
begin
inherited Create;
FJDevice := AJDevice;
FEventUUIDS := TEvent.Create;
end;
destructor TAndroidBluetoothDevice.Destroy;
begin
FEventUUIDS.Free;
FJDevice := nil;
inherited;
end;
function TAndroidBluetoothDevice.DoCreateClientSocket(const AUUID: TGUID; Secure: Boolean): TBluetoothSocket;
var
LJUUID: JUUID;
LJSocket: JBluetoothSocket;
begin
Result := nil;
LJUUID := BluetoothUuidToJUuid(AUUID);
if Secure then
LJSocket := FJDevice.createRfcommSocketToServiceRecord(LJUUID)
else
LJSocket := FJDevice.createInsecureRfcommSocketToServiceRecord(LJUUID);
if LJSocket <> nil then
Result := TAndroidBluetoothSocket.Create(LJSocket);
end;
function TAndroidBluetoothDevice.DoGetServices: TBluetoothServiceList;
var
LJArray: TJavaObjectArray;
LParcel: JParcelUuid;
I: Integer;
LLength: Integer;
LService: TBluetoothService;
begin
FServiceList.Clear;
try
{ API Level 15 }
if TOSVersion.Check(4, 0, 2) then
begin
FEventUUIDS.ResetEvent;
if FJDevice.fetchUuidsWithSdp then
FEventUUIDS.WaitFor(BluetoothServicesTimeout);
LJArray := FJDevice.getUuids;
LLength := 0;
if LJArray <> nil then
LLEngth := LJArray.Length;
for I := 0 to LLength - 1 do
begin
LParcel := TJParcelUuid.Wrap(LJArray.GetRawItem(I));
LService.UUID := JUuidToBluetoothUuid(LParcel.getUuid);
LService.Name := TAndroidBluetoothManager.GetKnownServiceName(LService.UUID);
FServiceList.Add(LService);
end;
end;
finally
Result := FServiceList;
end;
end;
function TAndroidBluetoothDevice.GetAddress: TBluetoothMacAddress;
begin
Result := JStringToString(FJDevice.getAddress);
end;
function TAndroidBluetoothDevice.GetBluetoothType: TBluetoothType;
begin
{ Check API Level 18 and ask for BT Type }
if TOSVersion.Check(4, 3) then
Result := TBluetoothType(FJDevice.getType)
else
Result := TBluetoothType.Classic;
end;
function TAndroidBluetoothDevice.GetClassDevice: Integer;
begin
Result := FJDevice.getBluetoothClass.getDeviceClass;
end;
function TAndroidBluetoothDevice.GetClassDeviceMajor: Integer;
begin
Result := FJDevice.getBluetoothClass.getMajorDeviceClass;
end;
function TAndroidBluetoothDevice.GetDeviceName: string;
begin
Result := JStringToString(FJDevice.getName);
end;
function TAndroidBluetoothDevice.GetOnDiscoverService: TDiscoverServiceEvent;
begin
Result := FDiscoverServiceEvent;
end;
function TAndroidBluetoothDevice.GetPaired: Boolean;
begin
Result := FJDevice.getBondState = TJBluetoothDevice.JavaClass.BOND_BONDED;
end;
function TAndroidBluetoothDevice.GetState: TBluetoothDeviceState;
var
LBondState: Integer;
begin
LBondState := FJDevice.getBondState;
case LBondState of
10:
Result := TBluetoothDeviceState.None;
11:
Result := TBluetoothDeviceState.None; // 11: Result := TBluetoothDeviceState.Pairing;
12:
Result := TBluetoothDeviceState.Paired;
else
raise EBluetoothAdapterException.CreateFmt(SBluetoothDeviceStateError, [GetDeviceName]);
end;
end;
function TAndroidBluetoothDevice.Pair: Boolean;
begin
{ API Level 19 }
if TOSVersion.Check(4, 4) then
Result := FJDevice.createBond
else
Result := False;
end;
procedure TAndroidBluetoothDevice.SetOnDiscoverService(const Value: TDiscoverServiceEvent);
begin
FDiscoverServiceEvent := Value;
end;
{ TAndroidBluetoothSocket }
procedure TAndroidBluetoothSocket.DoClose;
begin
FConnected := False;
if FJBluetoothSocket.isConnected then
begin
if FJOStream <> nil then
begin
FJOStream.close;
FJOStream := nil;
end;
if FJIStream <> nil then
begin
FJIStream.close;
FJIStream := nil;
end;
FJBluetoothSocket.close;
end;
end;
procedure TAndroidBluetoothSocket.DoConnect;
begin
// TBluetoothManager.Current.CancelDiscovery; // TODO -cCHECK
FJBluetoothSocket.connect;
FConnected := True;
end;
constructor TAndroidBluetoothSocket.Create(const ASocket: JBluetoothSocket);
begin
inherited Create;
FJBluetoothSocket := ASocket;
FJBytes := TJavaArray.Create(BUFFER_SIZE);
FSocketEvent := TEvent.Create;
FReaderEvent := TEvent.Create;
FDestroying := False;
FReader := TAndroidBluetoothSocket.TBluetoothSocketReader.Create(
Self,
BUFFER_SIZE);
FReader.Start;
FSocketEvent.WaitFor(SocketEventTimeout);
FSocketEvent.ResetEvent;
end;
destructor TAndroidBluetoothSocket.Destroy;
begin
FDestroying := True;
if FJOStream <> nil then
FJOStream.close;
if FJIStream <> nil then
FJIStream.close;
if FJBluetoothSocket.isConnected then
FJBluetoothSocket.close;
FReader.DisposeOf;
FReader := nil;
FSocketEvent.SetEvent;
FReaderEvent.Free;
FSocketEvent.Free;
FJOStream := nil;
FJIStream := nil;
FJBluetoothSocket := nil;
FJBytes.Free;
inherited;
end;
function TAndroidBluetoothSocket.GetConnected: Boolean;
begin
if TOsVersion.Check(4) then
Result := FConnected and (FJBluetoothSocket <> nil) and FJBluetoothSocket.isConnected
else
Result := FConnected;
end;
function TAndroidBluetoothSocket.GetRemoteDevice: TBluetoothDevice;
begin
Result := TAndroidBluetoothDevice.Create(FJBluetoothSocket.getRemoteDevice);
end;
function TAndroidBluetoothSocket.DoReceiveData(ATimeout: Cardinal): TBytes;
var
LReaded: Integer;
LCount: Integer;
LTime: Cardinal;
begin
if FJIStream = nil then
FJIStream := TJInputStream.Wrap(FJBluetoothSocket.getInputStream);
LCount := 0;
SetLength(
Result,
LCount);
if (FJIStream <> nil) and (FReader <> nil) then
begin
// Tell Reader to start reading;
FSocketEvent.ResetEvent;
FReaderEvent.SetEvent;
if Atimeout = 0 then
LTime := 1
else
LTime := ATimeout;
// Wait until Reader reads or timeout or error
if FSocketEvent.WaitFor(LTime) <> TWaitResult.wrSignaled then
Exit;
if FReader <> nil then
begin
LReaded := FReader.GetBufferedDataSize;
if LReaded < 0 then
raise EBluetoothSocketException.Create(SBluetoothRFChannelClosed)
else
begin
// Check for already readed data.
if LReaded > 0 then
begin
SetLength(
Result,
LCount + LReaded);
FReader.GetBufferedData(
Result,
LCount);
LCount := LCount + LReaded;
end;
// Check for available data.
repeat
try
if (FJIStream <> nil) and (FJIStream.available <> 0) then
LReaded := FJIStream.read(FJBytes)
else
Break;
except
LReaded := -1;
end;
if LReaded < 0 then
raise EBluetoothSocketException.Create(SBluetoothRFChannelClosed);
SetLength(Result, LCount + LReaded);
Move(PByte(FJBytes.Data)^, Result[LCount], LReaded);
LCount := LCount + LReaded;
until (LReaded < BUFFER_SIZE) or (FJIStream = nil);
end;
end;
end;
end;
procedure TAndroidBluetoothSocket.DoSendData(const AData: TBytes);
var
LJBytes: TJavaArray;
begin
inherited;
if FJOStream = nil then
FJOStream := TJOutputStream.Wrap(FJBluetoothSocket.getOutputStream);
if FJOStream <> nil then
begin
LJBytes := TBytesToTJavaArray(AData);
try
FJOStream.write(LJBytes);
finally
LJBytes.Free;
end;
end
else
EBluetoothSocketException.Create(SBluetoothSocketOutputStreamError);
end;
{ TAndroidBluetoothServerSocket }
function TAndroidBluetoothServerSocket.DoAccept(Timeout: Cardinal): TBluetoothSocket;
var
LSocket: JBluetoothSocket;
begin
try
LSocket := FJServerSocket.accept(Timeout);
Result := TAndroidBluetoothSocket.Create(LSocket);
except
Result := nil;
end;
end;
procedure TAndroidBluetoothServerSocket.DoClose;
begin
inherited;
FJServerSocket.close;
end;
constructor TAndroidBluetoothServerSocket.Create(const AServerSocket: JBluetoothServerSocket);
begin
inherited Create;
FJServerSocket := AServerSocket;
end;
destructor TAndroidBluetoothServerSocket.Destroy;
begin
FJServerSocket.close;
FJServerSocket := nil;
inherited;
end;
{ TBluetoothLeScanCallback }
function ServiceUUIDToGUID(const AData: TBytes): TGUID;
var
W: Word;
TB: TBytes;
I, Z : Integer;
begin
if Length(AData) = 2 then
begin
Result := BLUETOOTH_BASE_UUID;
WordRec(W).Hi := Adata[0];
WordRec(W).Lo := Adata[1];
Result.D1 := Cardinal(W);
end
else
begin
Z := 0;
SetLength(
TB,
Length(AData));
for I := Length(AData) - 1 downto 0 do
begin
TB[Z] := AData[I];
Inc(Z);
end;
Result := TGUID.Create(
PByte(TB)^,
TEndian.Big);
end;
end;
function ScanRecordToTScanResponse(const AScanRecord: TJavaArray; const AScanResponse: TScanResponse): TScanResponse;
var
I:Integer;
LDataLength, LTotalLength: Byte;
LScanRecord: TBytes;
LCurrentData: TBytes;
LAdType: Byte;
begin
if AScanResponse = nil then
Result := TScanResponse.Create
else
begin
Result := AScanResponse;
Result.Remove(TScanResponseKey.ManufacturerSpecificData);
end;
LScanRecord := TJavaArrayToTBytes(AScanRecord);
LTotalLength := Length(LScanRecord);
I := 0;
LDataLength := LScanRecord[I];
while I < LTotalLength do
begin
Inc(I);
if LDataLength > 0 then
begin
Assert(I < LTotalLength);
LAdType := LScanRecord[I];
Setlength(
LCurrentData,
LDataLength - 1);
// Ensure the scan record array is as large as the data block we are about to copy from it
Assert(I + LDataLength <= LTotalLength);
Move(
LScanRecord[I + 1],
LCurrentData[0],
LDataLength - 1);
if (TScanResponseKey(LAdType) = TScanResponseKey.ManufacturerSpecificData) then
begin
if Result.ContainsKey(TScanResponseKey.ManufacturerSpecificData) then
begin
Delete(
LCurrentData,
0,
2);
LCurrentData := Result.Items[TScanResponseKey(LAdType)] + LCurrentData;
end
else
Result.AddOrSetValue(
TScanResponseKey(LAdType),
LCurrentData);
end
else
Result.AddOrSetValue(
TScanResponseKey(LAdType),
LCurrentData);
end;
Inc(
I,
LDataLength);
if I < LTotalLength then
LDataLength := LScanRecord[I];
end;
end;
constructor TBluetoothLeScanCallback.Create(const AnAdapter: TAndroidBluetoothLEAdapter);
begin
inherited Create;
FAdapter := AnAdapter;
end;
procedure TBluetoothLeScanCallback.onLeScan(device: JBluetoothDevice; rssi: Integer; scanRecord: TJavaArray);
begin
ES_BTAPI_TRACE('TBluetoothLeScanCallback.onLeScan');
var LNew := False;
var LDevice := TAndroidBluetoothLEDevice(
TAndroidBluetoothLEManager.GetDeviceInList(
JStringToString(device.getAddress),
FAdapter.FManager.ALLDiscoveredDevices
)
);
if not Assigned(LDevice) then
begin
LDevice := TAndroidBluetoothLEDevice.Create(
device,
False,
FAdapter.FManager.ForceRefreshCachedDevices
);
LNew := True;
end else
LDevice.FJDevice := device;
LDevice.FAdvertisedData := ScanRecordToTScanResponse(
scanRecord,
LDevice.FAdvertisedData
);
LDevice.FLastRSSI := rssi;
FAdapter.DoDeviceDiscovered(
LDevice,
LNew,
FAdapter.m_scanFilterList
);
end;
{ TAndroidBluetoothGattServer }
constructor TAndroidBluetoothGattServer.Create(const AManager: TAndroidBluetoothLEManager; const AnAdapter: TAndroidBluetoothLEAdapter);
begin
inherited Create(AManager);
FJGattServerListener := TAndroidBluetoothGattServerListener.Create(Self);
FJGattServerCallback := TJRTLBluetoothGattServerCallback.JavaClass.init(FJGattServerListener);
FJGattServer := AManager.FJManager.openGattServer(
Amanager.FContext,
FJGattServerCallback);
end;
destructor TAndroidBluetoothGattServer.Destroy;
begin
FJGattServerCallback := nil;
FJGattServerListener := nil;
FJGattServer := nil;
inherited;
end;
function TAndroidBluetoothGattServer.DoCreateCharacteristic(const AService: TBluetoothGattService;
const AnUUID: TBluetoothUUID; const AProps: TBluetoothPropertyFlags;
const ADescription: string): TBluetoothGattCharacteristic;
begin
Result := TAndroidBluetoothGattService(AService).CreateCharacteristic(AnUUID, AProps, ADescription);
end;
function TAndroidBluetoothGattServer.DoCreateDescriptor(const ACharacteristic: TBluetoothGattCharacteristic;
const AnUUID: TBluetoothUUID): TBluetoothGattDescriptor;
begin
Result := TAndroidBluetoothGattCharacteristic(ACharacteristic).CreateDescriptor(AnUUID);
end;
function TAndroidBluetoothGattServer.DoCreateIncludedService(const AService: TBluetoothGattService;
const AnUUID: TBluetoothUUID; AType: TBluetoothServiceType): TBluetoothGattService;
begin
Result := TAndroidBluetoothGattService(AService).CreateIncludedService(AnUUID, AType);
end;
function TAndroidBluetoothGattServer.DoCreateService(const AnUUID: TBluetoothUUID; AType: TBluetoothServiceType): TBluetoothGattService;
var
LJService: JBluetoothGattService;
begin
LJService := TJBluetoothGattService.JavaClass.init(
BluetoothUuidToJUuid(AnUUID),
Integer(AType));
Result := TAndroidBluetoothGattService.Create(
AnUUID,
AType,
LJService);
end;
function TAndroidBluetoothGattServer.DoAddCharacteristic(const AService: TBluetoothGattService;
const ACharacteristic: TBluetoothGattCharacteristic): Boolean;
begin
Result := TAndroidBluetoothGattService(AService).DoAddCharacteristic(ACharacteristic);
end;
function TAndroidBluetoothGattServer.DoAddService(const AService: TBluetoothGattService): Boolean;
var
LJBluetoothGattService: JBluetoothGattService;
begin
LJBluetoothGattService := TAndroidBluetoothGattService(AService).FJService;
Result := FJGattServer.addService(LJBluetoothGattService);
end;
procedure TAndroidBluetoothGattServer.DoCharacteristicReadRequest(const ADevice: TBluetoothLEDevice; ARequestId,
AnOffset: Integer; const AGattCharacteristic: TBluetoothGattCharacteristic);
var
LStatus: TBluetoothGattStatus;
LOffset: Integer;
LValue: TBytes;
begin
LStatus := TBluetoothGattStatus(TJBluetoothGatt.JavaClass.GATT_SUCCESS);
if AnOffset = 0 then
DoOnCharacteristicRead(
Self,
AGattCharacteristic,
LStatus);
LValue := TAndroidBluetoothGattCharacteristic(AGattCharacteristic).DoGetValue;
LOffset := Length(LValue) - AnOffset;
if LOffset <= 0 then
begin
SetLength(
LValue,
0);
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
0,
LValue);
end
else
begin
if AnOffset > 0 then
begin
Move(
LValue[AnOffset],
LValue[0],
LOffset);
setLength(
LValue,
LOffset);
end;
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
AnOffset,
LValue);
end;
end;
procedure TAndroidBluetoothGattServer.DoCharacteristicWriteRequest(const ADevice: TBluetoothLEDevice; ARequestId: Integer;
const AGattCharacteristic: TBluetoothGattCharacteristic; APreparedWrite, AResponseNeeded: Boolean; AnOffset: Integer;
const AValue: TBytes);
var
LStatus: TBluetoothGattStatus;
LOffset: Integer;
LValue: TBytes;
LTotal: Integer;
begin
LStatus := TBluetoothGattStatus(TJBluetoothGatt.JavaClass.GATT_SUCCESS);
if AnOffset > 0 then
begin
LTotal := Length(AValue);
LOffset := AnOffset + LTotal;
LValue := TAndroidBluetoothGattCharacteristic(AGattCharacteristic).DoGetValue;
SetLength(
LValue,
LOffset);
Move(
AValue[0],
LValue[AnOffset],
LTotal);
TAndroidBluetoothGattCharacteristic(AGattCharacteristic).DoSetValue(LValue);
end
else
TAndroidBluetoothGattCharacteristic(AGattCharacteristic).DoSetValue(AValue);
FLastRWrittenGattCharacteristic := AGattCharacteristic;
if not APreparedWrite then
DoOnCharacteristicWrite(
ADevice,
AGattCharacteristic,
LStatus,
AValue);
if AResponseNeeded then
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
AnOffset,
AValue);
end;
procedure TAndroidBluetoothGattServer.DoClearServices;
begin
inherited;
StopAdvertising;
FJGattServer.clearServices;
FServices.Clear;
end;
procedure TAndroidBluetoothGattServer.DoClientConfigurationWrite(const ADescriptor: TBluetoothGattDescriptor;
const OldValue: TArray; const AClient: TBluetoothLEDevice);
function IsNotifyDisabled(const AValue: TArray): Boolean;
begin
Result := (Length(AValue) = 2) and (AValue[0] = $00) and (AValue[1] = $00);
end;
function IsNotifyEnabled(const AValue: TArray): Boolean;
begin
Result := (Length(AValue) = 2) and (AValue[0] = $01) and (AValue[1] = $00);
end;
function IsIndicateEnabled(const AValue: TArray): Boolean;
begin
Result := (Length(AValue) = 2) and (AValue[0] = $02) and (AValue[1] = $00);
end;
begin
if IsNotifyDisabled(OldValue) and (IsNotifyEnabled(ADescriptor.Value) or IsIndicateEnabled(ADescriptor.Value)) then
DoOnClientSubscribed(
Self,
AClient.Identifier,
ADescriptor.GetCharacteristic)
else
if IsNotifyDisabled(ADescriptor.Value) and (IsNotifyEnabled(OldValue) or IsIndicateEnabled(OldValue)) then
DoOnClientUnsubscribed(
Self,
AClient.Identifier,
ADescriptor.GetCharacteristic);
end;
procedure TAndroidBluetoothGattServer.DoClose;
begin
FJGattServer.close;
end;
function TAndroidBluetoothGattServer.DoConnect(const ADevice: TBluetoothLEDevice; AutoConnect: Boolean): Boolean;
begin
Result := FJGattServer.connect(
TAndroidBluetoothDevice(ADevice).FJDevice,
AutoConnect);
end;
procedure TAndroidBluetoothGattServer.DoDescriptorReadRequest(const ADevice: TBluetoothLEDevice; ARequestId,
AnOffset: Integer; const ADescriptor: TBluetoothGattDescriptor);
var
LStatus: TBluetoothGattStatus;
LOffset: Integer;
LValue: TBytes;
begin
LStatus := TBluetoothGattStatus(TJBluetoothGatt.JavaClass.GATT_SUCCESS);
LValue := TAndroidBluetoothGattDescriptor(ADescriptor).GetValue;
LOffset := Length(LValue) - AnOffset;
if LOffset <= 0 then
begin
SetLength(
LValue,
0);
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
0,
LValue);
end
else
begin
if AnOffset > 0 then
begin
Move(
LValue[AnOffset],
LValue[0],
LOffset);
setLength(
LValue,
LOffset);
end;
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
AnOffset,
LValue);
end;
end;
procedure TAndroidBluetoothGattServer.DoDescriptorWriteRequest(const ADevice: TBluetoothLEDevice; ARequestId: Integer;
const ADescriptor: TBluetoothGattDescriptor; APreparedWrite, AResponseNeeded: Boolean; AnOffset: Integer;
const AValue: TBytes);
var
LStatus: TBluetoothGattStatus;
LOffset: Integer;
LValue: TBytes;
LTotal: Integer;
LOldValue: TBytes;
begin
LStatus := TBluetoothGattStatus(TJBluetoothGatt.JavaClass.GATT_SUCCESS);
LOldValue := TAndroidBluetoothGattDescriptor(ADescriptor).Value;
if AnOffset > 0 then
begin
LTotal := Length(AValue);
LOffset := AnOffset + LTotal;
LValue := TAndroidBluetoothGattDescriptor(ADescriptor).DoGetValue;
SetLength(
LValue,
LOffset);
Move(
AValue[0],
LValue[AnOffset],
LTotal);
TAndroidBluetoothGattDescriptor(ADescriptor).DoSetValue(LValue);
end
else
TAndroidBluetoothGattDescriptor(ADescriptor).DoSetValue(AValue);
if ADescriptor.Kind = TBluetoothDescriptorKind.ClientConfiguration then
DoClientConfigurationWrite(
ADescriptor,
LOldValue,
ADevice);
if AResponseNeeded then
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
AnOffset,
AValue);
end;
function TAndroidBluetoothGattServer.FindCharacteristic(const AJCharacteristic: JBluetoothGattCharacteristic): TAndroidBluetoothGattCharacteristic;
var
I: Integer;
begin
Result := nil;
for I := 0 to FServices.Count - 1 do
begin
Result := TAndroidBluetoothGattService(FServices[I]).FindCharacteristic(AJCharacteristic);
if Result <> nil then
Break;
end;
end;
// procedure TAndroidBluetoothGattServer.DoDisconnect(const ADevice: TBluetoothLEDevice);
// begin
// FJGattServer.cancelConnection(TAndroidBluetoothDevice(ADevice).FJDevice);
// end;
procedure TAndroidBluetoothGattServer.DoExecuteWrite(const ADevice: TBluetoothLEDevice; ARequestId: Integer;
Execute: Boolean);
var
LStatus: TBluetoothGattStatus;
LValue: TBytes;
begin
inherited;
if Execute then
begin
LStatus := TBluetoothGattStatus(TJBluetoothGatt.JavaClass.GATT_SUCCESS);
LValue := TAndroidBluetoothGattCharacteristic(FLastRWrittenGattCharacteristic).DoGetValue;
DoOnCharacteristicWrite(
ADevice,
FLastRWrittenGattCharacteristic,
LStatus,
LValue);
DoSendResponse(
ADevice,
ARequestId,
Integer(LStatus),
Length(LValue),
LValue);
end;
end;
function TAndroidBluetoothGattServer.DoCreateAdvertiseData: TBluetoothLEAdvertiseData;
begin
Result := TAndroidBluetoothLEAdvertiseData.create(
TBluetoothGattServer(Self),
FManager.CurrentAdapter,
nil);
end;
function TAndroidBluetoothGattServer.DoGetServices: TBluetoothGattServiceList;
var
LJList: JList;
I: Integer;
LJService: JBluetoothGattService;
begin
if FServices.Count = 0 then
begin
LJList := FJGattServer.getServices;
for I := 0 to LJLIst.size - 1 do
begin
LJService := TJBluetoothGattService.Wrap(LJList.get(I));
FServices.Add(TAndroidBluetoothGattService.Create(JUuidToBluetoothUuid(LJService.getUuid),
TBluetoothServiceType(LJService.getType), LJService));
end;
end;
Result := FServices;
end;
function TAndroidBluetoothGattServer.DoIsAdvertising: Boolean;
begin
if AdvertiseData <> nil then
Result := TAndroidBluetoothLEAdvertiseData(AdvertiseData).FAdvertising
else
Result := False;
end;
procedure TAndroidBluetoothGattServer.DoSendResponse(const ADevice: TBluetoothLEDevice; ARequestId, AStatus,
AnOffset: Integer; const AValue: TBytes);
var
AResult: Boolean;
begin
AResult := FJGattServer.sendResponse(
TAndroidBluetoothLEDevice(ADevice).FJDevice,
ARequestId,
AStatus,
AnOffset,
TBytesToTJavaArray(AValue));
if not AResult then
raise EBluetoothDeviceException.CreateFmt('Cannot send response to device "%s" at "%s". %d', [ADevice.DeviceName,
ADevice.Address, AStatus]);
end;
procedure TAndroidBluetoothGattServer.DoServiceAdded(AStatus: TBluetoothGattStatus; const AService: TBluetoothGattService);
begin
inherited;
DoOnServiceAdded(
self,
AService,
TBluetoothGattStatus(AStatus));
end;
procedure TAndroidBluetoothGattServer.DoStartAdvertising;
begin
inherited;
if TOSVersion.Check(5) then
TAndroidBluetoothLEAdvertiseData(AdvertiseData).DoStartAdvertising;
end;
procedure TAndroidBluetoothGattServer.DoStopAdvertising;
begin
inherited;
TAndroidBluetoothLEAdvertiseData(AdvertiseData).DoStopAdvertising;
end;
procedure TAndroidBluetoothGattServer.DoUpdateCharacteristicValue(const ACharacteristic: TBluetoothGattCharacteristic);
var
I: Integer;
DevicesList: JList;
begin
DevicesList := TAndroidBluetoothLEManager(FManager).FJManager.getConnectedDevices(TJBluetoothProfile.JavaClass.GATT);
for I := 0 to DevicesList.size - 1 do
FJGattServer.notifyCharacteristicChanged(
JBluetoothDevice(DevicesList.get(I)),
(ACharacteristic as TAndroidBluetoothGattCharacteristic).FJCharacteristic,
false);
end;
function TAndroidBluetoothGattServer.FindDevice(const AJDevice: JBluetoothDevice): TBluetoothLEDevice;
begin
Result := TAndroidBluetoothLEManager.AddDeviceToList(
TAndroidBluetoothLEDevice.Create(AJDevice, True, True),
TAndroidBluetoothLEManager(TBluetoothLEManager.Current).AllDiscoveredDevices);
end;
function TAndroidBluetoothGattServer.FindService(const AJService: JBluetoothGattService): TBluetoothGattService;
var
LService: TAndroidBluetoothGattService;
I: Integer;
begin
Result := nil;
for I := 0 to FServices.Count - 1 do
begin
LService := TAndroidBluetoothGattService(FServices.Items[I]);
if LService.FJService.equals(AJService) then
Exit(LService);
end;
end;
{ TAndroidBluetoothService }
constructor TAndroidBluetoothGattService.Create(const AnUUID: TBluetoothUUID; AType: TBluetoothServiceType; const AJService: JBluetoothGattService);
begin
inherited Create;
if AJService = nil then
begin
FJService := TJBluetoothGattService.JavaClass.init(
BluetoothUuidToJUuid(AnUUID),
Integer(AType));
end
else
FJService := AJService;
end;
destructor TAndroidBluetoothGattService.Destroy;
begin
FJService := nil;
inherited;
end;
function TAndroidBluetoothGattService.DoAddCharacteristic(const ACharacteristic: TBluetoothGattCharacteristic): Boolean;
begin
Result := FJService.addCharacteristic(TAndroidBluetoothGattCharacteristic(ACharacteristic).FJCharacteristic);
end;
function TAndroidBluetoothGattService.DoAddIncludedService(const AService: TBluetoothGattService): Boolean;
begin
Result := FJService.addService(TAndroidBluetoothGattService(AService).FJService);
end;
function TAndroidBluetoothGattService.DoCreateCharacteristic(const AUuid: TBluetoothUUID;
APropertyFlags: TBluetoothPropertyFlags; const ADescription: string): TBluetoothGattCharacteristic;
var
LDescriptor: TBluetoothGattDescriptor;
begin
Result := TAndroidBluetoothGattCharacteristic.Create(
Self,
AUuid,
APropertyFlags);
if (TBluetoothProperty.Notify in APropertyFlags) or (TBluetoothProperty.Indicate in APropertyFlags) then
begin
LDescriptor := TAndroidBluetoothGattCharacteristic(Result).DoCreateDescriptor(CHAR_CLIENT_CONFIG);
if (TBluetoothProperty.Notify in APropertyFlags) then
LDescriptor.Notification := False;
if (TBluetoothProperty.Indicate in APropertyFlags) then
LDescriptor.Indication := False;
TAndroidBluetoothGattCharacteristic(Result).AddDescriptor(LDescriptor);
end;
if ADescription <> '' then
begin
LDescriptor := TAndroidBluetoothGattCharacteristic(Result).DoCreateDescriptor(CHAR_DESCRIPTION);
LDescriptor.UserDescription := ADescription;
TAndroidBluetoothGattCharacteristic(Result).AddDescriptor(LDescriptor);
end;
end;
function TAndroidBluetoothGattService.DoCreateIncludedService(const AnUUID: TBluetoothUUID;
AType: TBluetoothServiceType): TBluetoothGattService;
var
LJService: JBluetoothGattService;
begin
LJService := TJBluetoothGattService.JavaClass.init(
BluetoothUuidToJUuid(AnUUID),
Integer(AType));
Result := TAndroidBluetoothGattService.Create(
AnUUID,
AType,
LJService);
end;
function TAndroidBluetoothGattService.DoGetCharacteristics: TBluetoothGattCharacteristicList;
var
LJCharList: JList;
LJChar: JBluetoothGattCharacteristic;
LCharact: TBluetoothGattCharacteristic;
I: Integer;
begin
if FCharacteristics.Count = 0 then
begin
LJCharList := FJService.getCharacteristics;
if LJCharList <> nil then
begin
for I := 0 to LJCharList.size - 1 do
begin
LJChar := TJBluetoothGattCharacteristic.Wrap(LJCharList.get(I));
LCharact := TAndroidBluetoothGattCharacteristic.Create(
Self,
LJChar);
FCharacteristics.Add(LCharact);
end;
end;
end;
Result := FCharacteristics;
end;
function TAndroidBluetoothGattService.DoGetIncludedServices: TBluetoothGattServiceList;
var
LJList: JList;
LJItem: JBluetoothGattService;
LService: TBluetoothGattService;
I: Integer;
begin
if FIncludedServices.Count = 0 then
begin
LJList := FJService.getIncludedServices;
if LJList <> nil then
for I := 0 to LJList.size - 1 do
begin
LJItem := TJBluetoothGattService.Wrap(LJList.get(I));
LService := TAndroidBlueToothGattService.Create(
JUuidToBluetoothUuid(LJItem.getUuid),
TBluetoothServiceType(LJItem.getType),
LJItem);
FIncludedServices.Add(LService);
end;
end;
Result := FIncludedServices;
end;
function TAndroidBluetoothGattService.FindCharacteristic(
AJCharacteristic: JBluetoothGattCharacteristic): TAndroidBluetoothGattCharacteristic;
var
I: Integer;
LService: TAndroidBluetoothGattService;
LCharact: TAndroidBluetoothGattCharacteristic;
begin
Result := nil;
for I := 0 to FCharacteristics.Count - 1 do
begin
LCharact := TAndroidBluetoothGattCharacteristic(FCharacteristics.Items[I]);
if AJCharacteristic.equals(LCharact.FJCharacteristic) then
begin
Exit(LCharact);
end;
end;
for I := 0 to FIncludedServices.Count - 1 do
begin
LService := TAndroidBluetoothGattService(FIncludedServices.Items[I]);
Result := LService.FindCharacteristic(AJCharacteristic);
if Result <> nil then
Break;
end;
end;
function TAndroidBluetoothGattService.FindDescriptor(
AJDescriptor: JBluetoothGattDescriptor): TAndroidBluetoothGattDescriptor;
var
I: Integer;
LService: TAndroidBluetoothGattService;
LCharact: TAndroidBluetoothGattCharacteristic;
begin
Result := nil;
for I := 0 to FCharacteristics.Count - 1 do
begin
LCharact := TAndroidBluetoothGattCharacteristic(FCharacteristics.Items[I]);
Result := LCharact.FindDescriptor(AJDescriptor);
if Result <> nil then
Break;
end;
if Result = nil then
for I := 0 to FIncludedServices.Count - 1 do
begin
LService := TAndroidBluetoothGattService(FIncludedServices.Items[I]);
Result := LService.FindDescriptor(AJDescriptor);
if Result <> nil then
Break;
end;
end;
function TAndroidBluetoothGattService.GetServiceType: TBluetoothServiceType;
begin
Result := TBluetoothServiceType(FJService.getType);
end;
function TAndroidBluetoothGattService.GetServiceUUID: TBluetoothUUID;
begin
Result := JUuidToBluetoothUuid(FJService.getUuid);
end;
{ TAndroidBluetoothGattCharacteristic }
constructor TAndroidBluetoothGattCharacteristic.Create(const AService: TBluetoothGattService; AJCharacteristic: JBluetoothGattCharacteristic);
begin
inherited Create(AService);
FJCharacteristic := AJCharacteristic;
end;
constructor TAndroidBluetoothGattCharacteristic.Create(const AService: TBluetoothGattService; const AUUID: TBluetoothUUID;
AProperties: TBluetoothPropertyFlags);
begin
inherited Create(AService);
FJCharacteristic := TJBluetoothGattCharacteristic.JavaClass.init(
BluetoothUuidToJUuid(AUUID),
PropertiesToInteger(AProperties),
TJBluetoothGattCharacteristic.JavaClass.PERMISSION_READ
or TJBluetoothGattCharacteristic.JavaClass.PERMISSION_WRITE);
end;
destructor TAndroidBluetoothGattCharacteristic.Destroy;
begin
FJCharacteristic := nil;
inherited;
end;
function TAndroidBluetoothGattCharacteristic.DoAddDescriptor(const ADescriptor: TBluetoothGattDescriptor): Boolean;
begin
Result := FJCharacteristic.addDescriptor(TAndroidBluetoothGattDescriptor(ADescriptor).FJDescriptor);
end;
function TAndroidBluetoothGattCharacteristic.DoCreateDescriptor(const AUUID: TBluetoothUUID): TBluetoothGattDescriptor;
var
LJDesc: JBluetoothGattDescriptor;
LPermissions: Integer;
begin
Result := nil;
LPermissions := TJBluetoothGattDescriptor.JavaClass.PERMISSION_READ;
if (AUUID = CHAR_EXTENDEDPROPERTIES) or (AUUID = CHAR_DESCRIPTION) or (AUUID = CHAR_CLIENT_CONFIG) or
(AUUID = CHAR_SERVERCONFIGURATIONFORMAT) then
LPermissions := LPermissions or TJBluetoothGattDescriptor.JavaClass.PERMISSION_WRITE;
LJDesc := TJBluetoothGattDescriptor.JavaClass.init(
BluetoothUuidToJUuid(AUUID),
LPermissions);
if LJDesc <> nil then
Result := TAndroidBluetoothGattDescriptor.Create(
Self,
LJDesc);
end;
function TAndroidBluetoothGattCharacteristic.DoGetDescriptors: TBluetoothGattDescriptorList;
var
LJDescList: JList;
LJDescriptor: JBluetoothGattDescriptor;
LDescriptor: TAndroidBluetoothGattDescriptor;
I: Integer;
begin
if FDescriptors.Count = 0 then
begin
LJDescList := FJCharacteristic.getDescriptors;
if LJDescList <> nil then
for I := 0 to LJDescList.size - 1 do
begin
LJDescriptor := TJBluetoothGattDescriptor.Wrap(LJDescList.get(I));
LDescriptor := TAndroidBluetoothGattDescriptor.Create(
Self,
LJDescriptor);
FDescriptors.Add(LDescriptor);
end;
end;
Result := FDescriptors;
end;
function TAndroidBluetoothGattCharacteristic.DoGetValue: TBytes;
var
LCharacteristicBytes: TJavaArray;
begin
LCharacteristicBytes := FJCharacteristic.getValue;
try
Result := TJavaArrayToTBytes(LCharacteristicBytes);
finally
LCharacteristicBytes.Free;
end;
end;
function TAndroidBluetoothGattCharacteristic.FindDescriptor(
const AJDescriptor: JBluetoothGattDescriptor): TAndroidBluetoothGattDescriptor;
var
I: Integer;
LDescrip: TAndroidBluetoothGattDescriptor;
begin
Result := nil;
for I := 0 to FDescriptors.Count - 1 do
begin
LDescrip := TAndroidBluetoothGattDescriptor(FDescriptors.Items[I]);
if AJDescriptor.equals(LDescrip.FJDescriptor) then
begin
Exit(LDescrip);
end;
end;
end;
procedure TAndroidBluetoothGattCharacteristic.DoSetValue(const AValue: TBytes);
var
LCharacteristicBytes: TJavaArray;
begin
LCharacteristicBytes := TBytesToTJavaArray(AValue);
try
FJCharacteristic.setValue(LCharacteristicBytes);
finally
LCharacteristicBytes.Free;
end;
end;
function TAndroidBluetoothGattCharacteristic.GetProperties: TBluetoothPropertyFlags;
var
LProps: Integer;
begin
Result := [];
LProps := FJCharacteristic.getProperties;
if (LProps and PROPERTY_BROADCAST) = PROPERTY_BROADCAST then
Include(
Result,
TBluetoothProperty.Broadcast);
if (LProps and PROPERTY_READ) = PROPERTY_READ then
Include(
Result,
TBluetoothProperty.Read);
if (LProps and PROPERTY_WRITE_NO_RESPONSE) = PROPERTY_WRITE_NO_RESPONSE then
Include(
Result,
TBluetoothProperty.WriteNoResponse);
if (LProps and PROPERTY_WRITE) = PROPERTY_WRITE then
Include(
Result,
TBluetoothProperty.Write);
if (LProps and PROPERTY_NOTIFY) = PROPERTY_NOTIFY then
Include(
Result,
TBluetoothProperty.Notify);
if (LProps and PROPERTY_INDICATE) = PROPERTY_INDICATE then
Include(
Result,
TBluetoothProperty.Indicate);
if (LProps and PROPERTY_SIGNED_WRITE) = PROPERTY_SIGNED_WRITE then
Include(
Result,
TBluetoothProperty.SignedWrite);
if (LProps and PROPERTY_EXTENDED_PROPS) = PROPERTY_EXTENDED_PROPS then
Include(
Result,
TBluetoothProperty.ExtendedProps);
end;
function TAndroidBluetoothGattCharacteristic.GetUUID: TBluetoothUUID;
begin
Result := JUuidToBluetoothUuid(FJCharacteristic.getUuid);
end;
{ TAndroidBluetoothGattServerListener }
constructor TAndroidBluetoothGattServerListener.Create(const AServer: TAndroidBluetoothGattServer);
begin
inherited Create;
FGattServer := AServer;
end;
destructor TAndroidBluetoothGattServerListener.Destroy;
begin
inherited;
end;
procedure TAndroidBluetoothGattServerListener.onCharacteristicReadRequest(device: JBluetoothDevice; requestId,
offset: Integer; characteristic: JBluetoothGattCharacteristic);
var
LGattDevice: TBluetoothLEDevice;
begin
LGattDevice := FGattServer.FindDevice(device);
FGattServer.DoCharacteristicReadRequest(
LGattDevice,
requestId,
offset,
FGattServer.FindCharacteristic(characteristic));
end;
procedure TAndroidBluetoothGattServerListener.onCharacteristicWriteRequest(device: JBluetoothDevice; requestId: Integer;
characteristic: JBluetoothGattCharacteristic; preparedWrite, responseNeeded: Boolean; offset: Integer;
value: TJavaArray);
var
LGattDevice: TBluetoothLEDevice;
LAndroidBluetoothGattCharacteristic: TAndroidBluetoothGattCharacteristic;
begin
LGattDevice := FGattServer.FindDevice(device);
LAndroidBluetoothGattCharacteristic := FGattServer.FindCharacteristic(characteristic);
FGattServer.DoCharacteristicWriteRequest(
LGattDevice,
requestId,
LAndroidBluetoothGattCharacteristic,
preparedWrite,
responseNeeded,
offset,
TJavaArrayToTBytes(value));
FGattServer.DoUpdateCharacteristicValue(TBluetoothGattCharacteristic(LAndroidBluetoothGattCharacteristic));
end;
procedure TAndroidBluetoothGattServerListener.onConnectionStateChange(device: JBluetoothDevice; status, newState: Integer);
const
STATE_CONNECTED = 2;
STATE_DISCONNECTED = 0;
begin
case newState of
STATE_CONNECTED:
begin
FGattServer.DoOnConnectedDevice(
FGattServer,
FGattServer.FindDevice(device)
);
end;
STATE_DISCONNECTED:
FGattServer.DoOnDisconnectDevice(
FGattServer,
FGattServer.FindDevice(device)
);
else
//
end;
end;
procedure TAndroidBluetoothGattServerListener.onDescriptorReadRequest(device: JBluetoothDevice; requestId,
offset: Integer; descriptor: JBluetoothGattDescriptor);
var
LGattDevice: TBluetoothLEDevice;
LBluetoothGattServiceList: TBluetoothGattServiceList;
LBluetoothGattCharacteristicList: TBluetoothGattCharacteristicList;
LBluetoothGattDescriptor: TBluetoothGattDescriptor;
I, Z : Integer;
LCharactID: TBluetoothUUID;
begin
LGattDevice := FGattServer.FindDevice(device);
LBluetoothGattServiceList := TBluetoothGattServer(FGattServer).GetServices;
LCharactID := JUuidToBluetoothUuid(descriptor.getCharacteristic.getUuid);
LBluetoothGattDescriptor := nil;
for I := 0 to LBluetoothGattServiceList.Count - 1 do
begin
LBluetoothGattCharacteristicList := LBluetoothGattServiceList[I].Characteristics;
for Z := 0 to LBluetoothGattCharacteristicList.Count - 1 do
begin
if LBluetoothGattCharacteristicList[Z].UUID = LCharactID then
LBluetoothGattDescriptor := LBluetoothGattCharacteristicList[Z].GetDescriptor(JUuidToBluetoothUuid(descriptor.getUuid));
if LBluetoothGattDescriptor <> nil then
Break;
end;
if LBluetoothGattDescriptor <> nil then
Break;
end;
FGattServer.DoDescriptorReadRequest(
LGattDevice,
requestId,
offset,
LBluetoothGattDescriptor);
end;
procedure TAndroidBluetoothGattServerListener.onDescriptorWriteRequest(device: JBluetoothDevice; requestId: Integer;
descriptor: JBluetoothGattDescriptor; preparedWrite, responseNeeded: Boolean; offset: Integer;
value: TJavaArray);
var
LGattDevice: TBluetoothLEDevice;
LBluetoothGattServiceList: TBluetoothGattServiceList;
LBluetoothGattCharacteristicList: TBluetoothGattCharacteristicList;
LBluetoothGattDescriptor: TBluetoothGattDescriptor;
I, Z : Integer;
LCharactID: TBluetoothUUID;
begin
LGattDevice := FGattServer.FindDevice(device);
LBluetoothGattServiceList := TBluetoothGattServer(FGattServer).GetServices;
LCharactID := JUuidToBluetoothUuid(descriptor.getCharacteristic.getUuid);
LBluetoothGattDescriptor := nil;
for I := 0 to LBluetoothGattServiceList.Count - 1 do
begin
LBluetoothGattCharacteristicList := LBluetoothGattServiceList[I].Characteristics;
for Z := 0 to LBluetoothGattCharacteristicList.Count - 1 do
begin
if LBluetoothGattCharacteristicList[Z].UUID = LCharactID then
LBluetoothGattDescriptor := LBluetoothGattCharacteristicList[Z].GetDescriptor(JUuidToBluetoothUuid(descriptor.getUuid));
if LBluetoothGattDescriptor <> nil then
Break;
end;
if LBluetoothGattDescriptor <> nil then
Break;
end;
FGattServer.DoDescriptorWriteRequest(
LGattdevice,
requestId,
LBluetoothGattDescriptor,
preparedWrite,
responseNeeded,
offset,
TJavaArrayToTBytes(value));
FGattServer.DoUpdateCharacteristicValue(TBluetoothGattCharacteristic(FGattServer.FindCharacteristic(descriptor.getCharacteristic)));
end;
procedure TAndroidBluetoothGattServerListener.onExecuteWrite(device: JBluetoothDevice; requestId: Integer;
execute: Boolean);
var
LGattDevice: TBluetoothLEDevice;
begin
LGattDevice := FGattServer.FindDevice(device);
FGattServer.DoExecuteWrite(
LGattDevice,
requestId,
execute);
end;
procedure TAndroidBluetoothGattServerListener.onServiceAdded(AStatus: Integer; service: JBluetoothGattService);
begin
FGattServer.DoServiceAdded(
TBluetoothGattStatus(AStatus),
FGattServer.FindService(service));
end;
{ TAndroidBluetoothGattListener }
constructor TAndroidBluetoothGattListener.Create(const ALEDevice: TAndroidBluetoothLEDevice);
begin
inherited Create;
FGatt := ALEDevice;
end;
destructor TAndroidBluetoothGattListener.Destroy;
begin
FGatt := nil;
inherited;
end;
procedure TAndroidBluetoothGattListener.onCharacteristicChanged(gatt: JBluetoothGatt;
characteristic: JBluetoothGattCharacteristic);
var
LGattChar: TBluetoothGattCharacteristic;
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onCharacteristicChanged');
LGattChar := FGatt.FindCharacteristic(characteristic);
if LGattChar <> nil then
FGatt.DoOnCharacteristicRead(
LGattChar,
TBluetoothGattStatus.Success)
else
raise EBluetoothLECharacteristicException.CreateFmt(SBluetoothLECanNotFindCharacteristic, [JUuidToBluetoothUuid(characteristic.getUuid).ToString]);
end;
procedure TAndroidBluetoothGattListener.onCharacteristicRead(gatt: JBluetoothGatt;
characteristic: JBluetoothGattCharacteristic; status: Integer);
var
LGattChar: TAndroidBluetoothGattCharacteristic;
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onCharacteristicRead');
Exclude(
FGatt.m_state,
TAndroidBluetoothLEDevice.DeviceActivity.Reading
);
LGattChar := FGatt.FindCharacteristic(characteristic);
if LGattChar <> nil then
FGatt.DoOnCharacteristicRead(
LGattChar,
TBluetoothGattStatus(status)
)
else
raise EBluetoothLECharacteristicException.CreateFmt(SBluetoothLECanNotFindCharacteristic, [JUuidToBluetoothUuid(characteristic.getUuid).ToString]);
end;
procedure TAndroidBluetoothGattListener.onCharacteristicWrite(gatt: JBluetoothGatt;
characteristic: JBluetoothGattCharacteristic; status: Integer);
var
LGattChar: TAndroidBluetoothGattCharacteristic;
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onCharacteristicWrite');
Exclude(
FGatt.m_state,
TAndroidBluetoothLEDevice.DeviceActivity.Writing
);
LGattChar := FGatt.FindCharacteristic(characteristic);
if LGattChar <> nil then
begin
FGatt.DoOnCharacteristicWrite(
LGattChar,
TBluetoothGattStatus(status)
);
end else
raise EBluetoothLECharacteristicException.CreateFmt(SBluetoothLECanNotFindCharacteristic, [JUuidToBluetoothUuid(characteristic.getUuid).ToString]);
end;
class function TAndroidBluetoothGattListener.connectionStatusStringGet(status : Integer) : UnicodeString;
begin
case TGattConnectionErrorStatus(status) of
TGattConnectionErrorStatus.OK:
Exit('Connection/Disconnection is OK');
TGattConnectionErrorStatus.TIMEOUT:
Exit('Connection timeout, or device went out of range');
TGattConnectionErrorStatus.TERMINATED_BY_PEER:
Exit('Connection is terminated by peer device');
TGattConnectionErrorStatus.ERROR_BONDING_OR_SVC_DISCOVERY:
Exit('Error encountered bonding or discovering services');
TGattConnectionErrorStatus.ERROR_62,
TGattConnectionErrorStatus.ERROR_133:
Exit('Error connecting, device is not found');
end;
Result := Format(
'Unknown Connection/Disconnection status:%d',
[status]
);
end;
class function TAndroidBluetoothGattListener.connectionStateStringGet(state : Integer) : UnicodeString;
begin
if state = TJBluetoothProfile.JavaClass.STATE_DISCONNECTED then
Exit('STATE_DISCONNECTED')
else if state = TJBluetoothProfile.JavaClass.STATE_CONNECTING then
Exit('STATE_CONNECTEDING')
else if state = TJBluetoothProfile.JavaClass.STATE_CONNECTED then
Exit('STATE_CONNECTED')
else if state = TJBluetoothProfile.JavaClass.STATE_DISCONNECTING then
Exit('STATE_DISCONNECTING')
else
Exit(
Format(
'Unknown Connection state:%d',
[state]
)
);
end;
procedure TAndroidBluetoothGattListener.onConnectionStateChange(gatt: JBluetoothGatt; status, newState: Integer);
begin
ES_BTAPI_TRACE(
'TAndroidBluetoothGattListener.onConnectionStateChange, newState:%s, status:%s',
[
connectionStateStringGet(newState),
connectionStatusStringGet(status)
]
);
if TJBluetoothGatt.JavaClass.GATT_SUCCESS <> status then
begin
if status = Integer(TGattConnectionErrorStatus.ERROR_BONDING_OR_SVC_DISCOVERY) then
FGatt.unpair();
FGatt.internalClose();
FGatt.internalNotifyDisconnected();
Exit();
end;
if TJBluetoothProfile.JavaClass.STATE_CONNECTED = newState then
begin
FGatt.handleConnectionAndBonding();
end else if TJBluetoothProfile.JavaClass.STATE_DISCONNECTED = newState then
begin
FGatt.internalClose();
FGatt.internalNotifyDisconnected();
end;
end;
procedure TAndroidBluetoothGattListener.onDescriptorRead(gatt: JBluetoothGatt; descriptor: JBluetoothGattDescriptor;
status: Integer);
var
LGattDesc: TAndroidBluetoothGattDescriptor;
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onDescriptorRead');
Exclude(
FGatt.m_state,
TAndroidBluetoothLEDevice.DeviceActivity.ReadingDcr
);
LGattDesc := FGatt.FindDescriptor(descriptor);
if LGattDesc <> nil then
FGatt.DoOnDescriptorRead(
LGattDesc,
TBluetoothGattStatus(status))
else
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothLECanNotFindDescriptor, [JUuidToBluetoothUuid(descriptor.getUuid).ToString]);
end;
procedure TAndroidBluetoothGattListener.onDescriptorWrite(gatt: JBluetoothGatt; descriptor: JBluetoothGattDescriptor;
status: Integer);
var
LGattDesc: TAndroidBluetoothGattDescriptor;
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onDescriptorWrite');
Exclude(
FGatt.m_state,
TAndroidBluetoothLEDevice.DeviceActivity.WritingDcr
);
LGattDesc := FGatt.FindDescriptor(descriptor);
if LGattDesc <> nil then
begin
FGatt.DoOnDescriptorWrite(
LGattDesc,
TBluetoothGattStatus(status));
end else
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothLECanNotFindDescriptor, [JUuidToBluetoothUuid(descriptor.getUuid).ToString]);
end;
procedure TAndroidBluetoothGattListener.onReadRemoteRssi(gatt: JBluetoothGatt; rssi, status: Integer);
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onReadRemoteRssi');
Exclude(
FGatt.m_state,
TAndroidBluetoothLEDevice.DeviceActivity.ReadingRSSI
);
FGatt.FLastRSSI := rssi;
FGatt.DoOnReadRssi(
FGatt,
rssi,
TBluetoothGattStatus(status)
);
end;
procedure TAndroidBluetoothGattListener.onReliableWriteCompleted(gatt: JBluetoothGatt; status: Integer);
begin
ES_BTAPI_TRACE('TAndroidBluetoothGattListener.onReliableWriteCompleted');
Exclude(
FGatt.m_state,
TAndroidBluetoothLEDevice.DeviceActivity.ReliableWriting
);
FGatt.DoOnReliableWriteCompleted(TBluetoothGattStatus(status));
end;
procedure TAndroidBluetoothGattListener.onServicesDiscovered(gatt: JBluetoothGatt; status: Integer);
begin
ES_BTAPI_TRACE(
'TAndroidBluetoothGattListener.onServicesDiscovered, status->%s',
[EsBleManager.gattStatusString(TBluetoothGattStatus(status))]
);
FGatt.servicesDiscovered(status);
end;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
{ TAndroidBluetoothGattClient }
constructor TAndroidBluetoothLEDevice.Create(const AJDevice: JBluetoothDevice; AutoConnect: Boolean;
AForceRefreshCachedServices: Boolean);
begin
inherited Create(AutoConnect);
FJDevice := AJDevice;
m_state := [];
FForceRefreshCachedServices := AForceRefreshCachedServices;
FPaired := False;
end;
class constructor TAndroidBluetoothLEDevice.Create;
const
ClassName = 'android/bluetooth/BluetoothGatt';
MethodName = 'refresh';
Sig = '()Z';
begin
FRefreshMethod := CheckApiMethod(
ClassName,
MethodName,
Sig
);
end;
destructor TAndroidBluetoothLEDevice.Destroy;
begin
internalDisconnect(); //< Disconnect in sync here
FJGatt := nil;
FJGattCallback := nil;
FJGattListener := nil;
inherited;
FJDevice := nil;
end;
procedure TAndroidBluetoothLEDevice.internalConnect();
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.internalConnect');
Assert(Assigned(FJDevice));
Assert(not Assigned(FJGatt));
if isBusy then
Exit();
var jctx := TAndroidBluetoothLEManager(TBluetoothLEManager.Current).FContext;
Assert(Assigned(jctx));
Include(
m_state,
DeviceActivity.Connecting
);
if TOSVersion.Check(6) then // < Android 6 and higher, to avoid GATT error 133
FJGatt := FJDevice.connectGatt(
jctx,
FAutoConnect,
FJGattCallback,
TJBluetoothDevice.JavaClass.TRANSPORT_LE
)
else
FJGatt := FJDevice.connectGatt(
jctx,
FAutoConnect,
FJGattCallback
);
if not Assigned(FJGatt) then
Exclude(
m_state,
DeviceActivity.Connecting
);
end;
function TAndroidBluetoothLEDevice.internalGetGattClient(): JBluetoothGatt;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.internalGetGattClient()');
if not Assigned(FJGatt) then
begin
if not Assigned(FJGattListener) then
FJGattListener := TAndroidBluetoothGattListener.Create(Self);
if not Assigned(FJGattCallback) then
FJGattCallback := TJRTLBluetoothGattCallback.JavaClass.init(FJGattListener);
internalConnect();
end;
Result := FJGatt;
end;
procedure TAndroidBluetoothLEDevice.servicesDiscovered(status : Integer);
begin
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.servicesDiscovered(status:%s)',
[
EsBleManager.gattStatusString(TBluetoothGattStatus(status))
]
);
m_lastStatus := TBluetoothGattStatus(status);
if
(status = $81) or //< Internal gatt error
(status = $85) //< Gatt error
then begin
ES_BTAPI_TRACE('.. Error encountered, disconnectin');
internalDisconnect();
Exit();
end;
if not (DeviceActivity.SvcScanning in m_state) then
Exit();
Exclude(
m_state,
DeviceActivity.SvcScanning
);
ensureServicesActualized();
DoOnServicesDiscovered(
Self,
FServices
);
end;
procedure TAndroidBluetoothLEDevice.bondingChanged(statePrev, newState : Integer);
begin
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.bondingChanged(statePrev: %d, newState: %d)',
[
statePrev,
newState
]
);
if newState = TJBluetoothDevice.JavaClass.BOND_BONDED then
begin
ES_BTAPI_TRACE('.. Bonded Successfully');
// If we got BOND_BONDED after onConnected, we'll be still in connecting stage
// and should issue proper connection notification here
//
m_connStatus := TBluetoothDeviceState.Paired;
internalNotifyConnected(false); //< Notify without long delay here
end else if newState = TJBluetoothDevice.JavaClass.BOND_NONE then
begin
if statePrev = TJBluetoothDevice.JavaClass.BOND_BONDING then
begin
// Bonding failed
ES_BTAPI_TRACE('.. Bonding failed');
end else begin
// Bond lost
ES_BTAPI_TRACE('.. Bonding lost');
end;
internalDisconnect();
end;
end;
procedure TAndroidBluetoothLEDevice.DoAbortReliableWrite;
begin
if
IsConnected and
(DeviceActivity.ReliableWriting in m_state)
then begin
FJGatt.abortReliableWrite();
Exclude(
m_state,
DeviceActivity.ReliableWriting
);
end;
end;
function TAndroidBluetoothLEDevice.DoBeginReliableWrite: Boolean;
begin
if
IsConnected and
not isBusy
then begin
Include(
m_state,
DeviceActivity.ReliableWriting
);
Result := FJGatt.beginReliableWrite();
if not Result then
Exclude(
m_state,
DeviceActivity.ReliableWriting
);
end else
Result := False;
end;
procedure TAndroidBluetoothLEDevice.clearServicesCache();
begin
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.clearServicesCache() FRefreshMethod->%s',
[BoolToStr(FRefreshMethod, true)]
);
if not FRefreshMethod then
Exit();
FJGatt.refresh();
// Because refresh method is a "tricky" and does not provide a callback we should wait a little
TThread.Sleep(c_svcCacheRefreshTmo);
end;
procedure TAndroidBluetoothLEDevice.unpair();
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.unpair() - TODO!');
// if not Assigned(FJDevice) then
// Exit();
//
// var penv := TJNIResolver.GetJNIEnv();
// if not Assigned(penv) then
// Exit();
//
// var unpairID := penv^.GetMethodID(
// penv,
// FJDevice.getClass(),
// 'removeBond',
//
// );
end;
function TAndroidBluetoothLEDevice.DoDiscoverServices: Boolean;
begin
internalGetGattClient();
if not IsConnected then
Exit(false);
if DeviceActivity.SvcScanning in m_state then
Exit(true);
Include(
m_state,
DeviceActivity.SvcScanning
);
FServices.Clear();
if FForceRefreshCachedServices then
clearServicesCache();
Result := FJGatt.discoverServices();
if not Result then
Exclude(
m_state,
DeviceActivity.SvcScanning
);
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.DoDiscoverServices->%s',
[BoolToStr(Result, true)]
);
end;
function TAndroidBluetoothLEDevice.DoExecuteReliableWrite: Boolean;
begin
if
IsConnected and
(DeviceActivity.ReliableWriting in m_state)
then begin
Result := FJGatt.executeReliableWrite;
if not Result then
Exclude(
m_state,
DeviceActivity.ReliableWriting
);
end else
Result := false;
end;
procedure TAndroidBluetoothLEDevice.internalNotifyDisconnected();
begin
m_state := [];
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.internalNotifyDisconnected(), current m_connStatus: %s',
[ TRttiEnumerationType.GetName(m_connStatus) ]
);
if m_connStatus <> TBluetoothDeviceState.None then
begin
m_connStatus := TBluetoothDeviceState.None;
if Assigned(OnDisconnect) then
OnDisconnect(Self);
end;
end;
procedure TAndroidBluetoothLEDevice.internalNotifyConnected(withDelay : Boolean);
begin
// We've connected, handle connection event with potential delay
var connEventDelay := 1;
if withDelay then
begin
connEventDelay := c_connDelayMax;
if TOSVersion.Check(7) then
connEventDelay := c_connDelay;
end;
EsUtilities.lazyCall(
procedure () begin
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.internalNotifyConnected(withDelay:%s), delay:%d, m_connStatus:%s',
[
BoolToStr(
withDelay,
true
),
connEventDelay,
TRttiEnumerationType.GetName(m_connStatus)
]
);
if DeviceActivity.Connecting in m_state then
begin
Exclude(
m_state,
DeviceActivity.Connecting
);
if Assigned(OnConnect) then
OnConnect(Self);
end;
end,
'Lazy FGatt.OnConnect',
connEventDelay
);
end;
class function TAndroidBluetoothLEDevice.bondStateStringGet(bondState : Integer) : UnicodeString;
begin
if bondState = TJBluetoothDevice.JavaClass.BOND_NONE then
Exit('BOND_NONE')
else if bondState = TJBluetoothDevice.JavaClass.BOND_BONDING then
Exit('BOND_BONDING')
else if bondState = TJBluetoothDevice.JavaClass.BOND_BONDED then
Exit('BOND_BONDED')
else
Exit(
Format(
'Unknown bondState:%d',
[bondState]
)
);
end;
procedure TAndroidBluetoothLEDevice.handleConnectionAndBonding();
begin
Assert(Assigned(FJDevice));
var bondState := FJDevice.getBondState();
ES_BTAPI_TRACE(
'TAndroidBluetoothLEDevice.handleConnectionAndBonding() bondState:%s',
[bondStateStringGet(bondState)]
);
// Handle bondState
if
(bondState = TJBluetoothDevice.JavaClass.BOND_NONE) or
(bondstate = TJBluetoothDevice.JavaClass.BOND_BONDED)
then begin
if bondState = TJBluetoothDevice.JavaClass.BOND_NONE then
m_connStatus := TBluetoothDeviceState.Connected
else if bondstate = TJBluetoothDevice.JavaClass.BOND_BONDED then
m_connStatus := TBluetoothDeviceState.Paired;
internalNotifyConnected(true); //< Notify with delay
end else if bondstate = TJBluetoothDevice.JavaClass.BOND_BONDING then
begin
// Bonding is ongoing, have to wait
ES_BTAPI_TRACE('.. Waiting for bonding to complete, will handle connection completion from bonding broadcast listener');
end;
end;
procedure TAndroidBluetoothLEDevice.updateServicesList();
procedure UpdateDescriptors(const jchx: JBluetoothGattCharacteristic; const dcrs: TBluetoothGattDescriptorList);
begin
var jdcrs := jchx.getDescriptors;
if jdcrs <> nil then
begin
for var I := 0 to jdcrs.size - 1 do
begin
var jdcr := TJBluetoothGattDescriptor.Wrap(jdcrs.get(I));
for var B := 0 to dcrs.Count - 1 do
begin
var dcr := TAndroidBluetoothGattDescriptor(dcrs[I]);
if jdcr.getUuid.compareTo(dcr.FJDescriptor.getUuid) = 0 then
begin
dcr.FJDescriptor := jdcr;
break;
end;
end;
end;
end;
end;
procedure UpdateCharacteristics(const jsvc: JBluetoothGattService; const chxs: TBluetoothGattCharacteristicList);
begin
var jchxs := jsvc.getCharacteristics;
if not Assigned(jchxs) then
Exit();
for var I := 0 to jchxs.size - 1 do
begin
var jchx := TJBluetoothGattCharacteristic.Wrap(jchxs.get(I));
for var B := 0 to chxs.Count - 1 do
begin
var chx := TAndroidBluetoothGattCharacteristic(chxs[I]);
if jchx.getUuid.compareTo(chx.FJCharacteristic.getUuid) = 0 then
begin
chx.FJCharacteristic := jchx;
if chx.FDescriptors.Count > 0 then
UpdateDescriptors(
jchx,
chx.FDescriptors
);
break;
end;
end;
end;
end;
procedure UpdateServicesList(const svcs: TBluetoothGattServiceList; const jsvcs: JList);
begin
if
not Assigned(jsvcs) or
not Assigned(svcs)
then
Exit();
for var I := 0 to jsvcs.size - 1 do
begin
var jsvc := TJBluetoothGattService.Wrap(jsvcs.get(I));
for var B := 0 to svcs.Count - 1 do
begin
var svc := TAndroidBluetoothGattService(svcs[B]);
if svc.FJService.getUuid.compareTo(jsvc.getUuid) = 0 then
begin
if not jsvc.getIncludedServices.isEmpty then
UpdateServicesList(
svcs[B].IncludedServices,
jsvc.getIncludedServices
);
svc.FJService := jsvc;
if svc.FCharacteristics.Count > 0 then
UpdateCharacteristics(
jsvc,
svc.FCharacteristics
);
break;
end;
end;
end;
end;
begin
try
TMonitor.Enter(FServices);
UpdateServicesList(
FServices,
FJGatt.getServices()
);
finally
TMonitor.Exit(FServices);
end;
end;
function TAndroidBluetoothLEDevice.DoReadCharacteristic(const ACharacteristic: TBluetoothGattCharacteristic): Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoReadCharacteristic');
Result := False;
if
IsConnected and
not isBusy
then begin
Include(
m_state,
DeviceActivity.Reading
);
Result := FJGatt.readCharacteristic(
TAndroidBluetoothGattCharacteristic(ACharacteristic).FJCharacteristic
);
if not Result then
Exclude(
m_state,
DeviceActivity.Reading
);
end;
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]);
end;
function TAndroidBluetoothLEDevice.DoReadDescriptor(const ADescriptor: TBluetoothGattDescriptor): Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoReadDescriptor');
Result := False;
if
IsConnected and
not isBusy
then begin
Include(
m_state,
DeviceActivity.ReadingDcr
);
Result := FJGatt.readDescriptor(TAndroidBluetoothGattDescriptor(ADescriptor).FJDescriptor);
if not Result then
Exclude(
m_state,
DeviceActivity.ReadingDcr
);
end;
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]);
end;
function TAndroidBluetoothLEDevice.DoReadRemoteRSSI: Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoReadRemoteRSSI');
Result := False;
if
IsConnected and
not isBusy
then begin
Include(
m_state,
DeviceActivity.ReadingRSSI
);
Result := FJGatt.readRemoteRssi;
if not Result then
Exclude(
m_state,
DeviceActivity.ReadingRSSI
);
end;
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]);
end;
function TAndroidBluetoothLEDevice.DoRequestMtu(AMtu: Integer): Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoRequestMtu');
Result := False;
if IsConnected then
Result := FJGatt.requestMtu(AMtu);
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]);
end;
function TAndroidBluetoothLEDevice.DoSetCharacteristicNotification(
const ACharacteristic: TBluetoothGattCharacteristic; Enable: Boolean): Boolean;
var
LDesc: TBluetoothGattDescriptor;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoSetCharacteristicNotification');
Result := false;
if
IsConnected and
not isBusy
then begin
if FJGatt.setCharacteristicNotification(TAndroidBluetoothGattCharacteristic(ACharacteristic).FJCharacteristic, Enable) then
begin
LDesc := ACharacteristic.GetDescriptor(CHAR_CLIENT_CONFIG);
if LDesc <> nil then
begin
if TBluetoothProperty.Notify in ACharacteristic.Properties then
LDesc.Notification := Enable
else
LDesc.Indication := Enable;
Result := WriteDescriptor(LDesc);
end;
end;
end;
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]
);
end;
function TAndroidBluetoothLEDevice.DoWriteCharacteristic(const ACharacteristic: TBluetoothGattCharacteristic): Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoWriteCharacteristic');
Result := false;
if
IsConnected and
not isBusy
then begin
Include(
m_state,
DeviceActivity.Writing
);
Result := FJGatt.writeCharacteristic(TAndroidBluetoothGattCharacteristic(ACharacteristic).FJCharacteristic);
if not Result then
Exclude(
m_state,
DeviceActivity.Writing
);
end;
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]);
end;
function TAndroidBluetoothLEDevice.DoWriteDescriptor(const ADescriptor: TBluetoothGattDescriptor): Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoWriteDescriptor');
Result := false;
if
IsConnected and
not isBusy
then begin
Include(
m_state,
DeviceActivity.WritingDcr
);
Result := FJGatt.writeDescriptor(TAndroidBluetoothGattDescriptor(ADescriptor).FJDescriptor);
if not Result then
Exclude(
m_state,
DeviceActivity.ReadingDcr
);
end;
ES_BTAPI_TRACE(
'.. -> %s',
[BoolToStr(Result, true)]
);
end;
function TAndroidBluetoothLEDevice.FindCharacteristic(
const AJCharacteristic: JBluetoothGattCharacteristic): TAndroidBluetoothGattCharacteristic;
var
I: Integer;
LService: TAndroidBluetoothGattService;
begin
Result := nil;
for I := 0 to FServices.Count - 1 do
begin
LService := TAndroidBluetoothGattService(FServices.Items[I]);
Result := LService.FindCharacteristic(AJCharacteristic);
if Result <> nil then
Break;
end;
end;
function TAndroidBluetoothLEDevice.FindDescriptor(
const AJDescriptor: JBluetoothGattDescriptor): TAndroidBluetoothGattDescriptor;
var
I: Integer;
LService: TAndroidBluetoothGattService;
begin
Result := nil;
for I := 0 to FServices.Count - 1 do
begin
LService := TAndroidBluetoothGattService(FServices.Items[I]);
Result := LService.FindDescriptor(AJDescriptor);
if Result <> nil then
Break;
end;
end;
function TAndroidBluetoothLEDevice.DoConnect: Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoConnect');
if
IsConnected or
(DeviceActivity.Connecting in m_state)
then
Exit(true);
Result := Assigned( internalGetGattClient() ); //< Implicitly initiate connection
end;
function TAndroidBluetoothLEDevice.DoCreateAdvertiseData: TBluetoothLEAdvertiseData;
begin
Result := TAndroidBluetoothLEAdvertiseData.Create(
nil,
nil,
TBluetoothLEDevice(Self));
end;
function TAndroidBluetoothLEDevice.GetAddress: TBluetoothMacAddress;
begin
Result := JStringToString(FJDevice.getAddress);
end;
function TAndroidBluetoothLEDevice.GetBluetoothType: TBluetoothType;
begin
{ Check API Level 18 and ask for BT Type }
if TOSVersion.Check(4, 3) then
Result := TBluetoothType(FJDevice.getType)
else
Result := TBluetoothType.Classic;
end;
function TAndroidBluetoothLEDevice.GetDeviceName: string;
begin
Result := JStringToString(FJDevice.getName);
end;
function TAndroidBluetoothLEDevice.GetIdentifier: string;
begin
Result := GetAddress;
end;
function TAndroidBluetoothLEDevice.GetIsConnected: Boolean;
begin
if not Assigned(FJDevice) then
raise EBluetoothLEDeviceException.Create(SBluetoothLEGetDeviceError);
if Assigned(FJGatt) then
Exit(
(
m_connStatus in [
TBluetoothDeviceState.Connected,
TBluetoothDeviceState.Paired
]
) and
not (DeviceActivity.Connecting in m_state)
);
Result := False;
end;
function TAndroidBluetoothLEDevice.isBusy() : Boolean;
begin
Result := ([] <> m_state);
end;
procedure TAndroidBluetoothLEDevice.ensureServicesActualized();
procedure GetServices(const parentSvc: TBluetoothGattService; const jsvcs: JList);
begin
if not Assigned(jsvcs) then
Exit();
for var I := 0 to jsvcs.size - 1 do
begin
var jsvc := TJBluetoothGattService.Wrap(jsvcs.get(I));
var svc := TAndroidBluetoothGattService.Create(
JUuidToBluetoothUuid(jsvc.getUuid),
TBluetoothServiceType(jsvc.getType),
jsvc
);
if not jsvc.getIncludedServices.isEmpty then
GetServices(
svc,
jsvc.getIncludedServices
);
if Assigned(parentSvc) then
parentSvc.CreateIncludedService(
svc.UUID,
svc.ServiceType
)
else
FServices.Add(svc);
end;
end;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.ensureServicesActualized()');
try
TMonitor.Enter(FServices);
if 0 = FServices.Count then
GetServices(
nil,
FJGatt.getServices
);
finally
TMonitor.Exit(FServices);
end;
updateServicesList();
end;
procedure TAndroidBluetoothLEDevice.internalDisconnect();
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.internalDisconnect()');
if not Assigned(FJGatt) then
begin
m_state := [];
Exit();
end;
Include(
m_state,
DeviceActivity.Disconnecting
);
FJGatt.disconnect(); // Final event will be issued from the connection listener
// We're not connected yet, cancel connection after some time, and clear internal device variable
if DeviceActivity.Connecting in m_state then
begin
TThread.Sleep(c_connCancelDelay);
// It may happen that we will not receive disconnect notification
// if stack is not yet connected
internalClose();
internalNotifyDisconnected();
end;
end;
procedure TAndroidBluetoothLEDevice.internalClose();
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.internalClose');
if not Assigned(FJGatt) then
begin
m_state := []; //< Reset all device activities
Exit();
end;
FJGatt.close();
FJGatt := nil;
FServices.Clear();
m_state := []; //< Reset all device activities
end;
function TAndroidBluetoothLEDevice.DoDisconnect: Boolean;
begin
ES_BTAPI_TRACE('TAndroidBluetoothLEDevice.DoDisconnect');
if DeviceActivity.Disconnecting in m_state then
Exit(true);
internalDisconnect();
Result := True;
end;
{ TAndroidBluetoothGattDescriptor }
constructor TAndroidBluetoothGattDescriptor.Create(const ACharacteristic: TBluetoothGattCharacteristic; AJDescriptor: JBluetoothGattDescriptor);
begin
inherited Create(ACharacteristic);
FJDescriptor := AJDescriptor;
end;
destructor TAndroidBluetoothGattDescriptor.Destroy;
begin
FJDescriptor := nil;
inherited;
end;
function TAndroidBluetoothGattDescriptor.DoGetBroadcasts: Boolean;
var
B: TBytes;
begin
if not(TBluetoothProperty.Broadcast in FCharacteristic.Properties) then
raise EBluetoothLEDescriptorException.CreateFmt('Characteristic Error Message: %s, %d',
[GetEnumName(TypeInfo(TBluetoothProperty), Ord(TBluetoothProperty.Broadcast))]);
B := GetValue;
Result := (Length(B) = 2) and (B[0] = $01) and (B[1] = $00);
end;
function TAndroidBluetoothGattDescriptor.DoGetExponent: ShortInt;
begin
Result := ShortInt(Value[1]);
end;
function TAndroidBluetoothGattDescriptor.DoGetFormat: TBluetoothGattFormatType;
begin
Result := TBluetoothGattFormatType(Value[0]);
end;
function TAndroidBluetoothGattDescriptor.DoGetFormatUnit: TBluetoothUUID;
var
B: TBytes;
LValue: Word;
begin
B := GetValue;
LValue := B[2] or (B[3] shl 8);
Result := BLUETOOTH_BASE_UUID;
Result.D1 := Cardinal(LValue);
end;
function TAndroidBluetoothGattDescriptor.DoGetIndication: Boolean;
var
B: TBytes;
begin
if not(TBluetoothProperty.Indicate in FCharacteristic.Properties) then
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothCharacteristicError,
[GetEnumName(TypeInfo(TBluetoothProperty), Ord(TBluetoothProperty.Indicate))]);
B := GetValue;
Result := (Length(B) = 2) and (B[0] = $02) and (B[1] = $00);
end;
function TAndroidBluetoothGattDescriptor.DoGetNotification: Boolean;
var
B: TBytes;
begin
if not(TBluetoothProperty.Notify in FCharacteristic.Properties) then
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothCharacteristicError,
[GetEnumName(TypeInfo(TBluetoothProperty), Ord(TBluetoothProperty.Notify))]);
B := GetValue;
Result := (Length(B) = 2) and (B[0] = $01) and (B[1] = $00);
end;
function TAndroidBluetoothGattDescriptor.DoGetReliableWrite: Boolean;
begin
Result := False;
end;
function TAndroidBluetoothGattDescriptor.DoGetUserDescription: string;
begin
Result := TEncoding.UTF8.GetString(Value);
end;
function TAndroidBluetoothGattDescriptor.DoGetValue: TBytes;
var
LDescriptorBytes: TJavaArray;
begin
LDescriptorBytes := FJDescriptor.getValue;
try
Result := TJavaArrayToTBytes(LDescriptorBytes);
finally
LDescriptorBytes.Free;
end;
end;
function TAndroidBluetoothGattDescriptor.DoGetWritableAuxiliaries: Boolean;
begin
Result := False;
end;
procedure TAndroidBluetoothGattDescriptor.DoSetBroadcasts(const Value: Boolean);
var
B: TBytes;
begin
if not(TBluetoothProperty.Broadcast in FCharacteristic.Properties) then
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothCharacteristicError,
[GetEnumName(TypeInfo(TBluetoothProperty), Ord(TBluetoothProperty.Broadcast))]);
SetLength(
B,
2);
if Value then
B[0] := $01
else
B[0] := $00;
B[1] := $00;
SetValue(B);
end;
procedure TAndroidBluetoothGattDescriptor.DoSetIndication(const Value: Boolean);
var
B: TBytes;
begin
if not(TBluetoothProperty.Indicate in FCharacteristic.Properties) then
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothCharacteristicError,
[GetEnumName(TypeInfo(TBluetoothProperty), Ord(TBluetoothProperty.Broadcast))]);
SetLength(
B,
2);
if Value then
B[0] := $02
else
B[0] := $00;
B[1] := $00;
SetValue(B);
end;
procedure TAndroidBluetoothGattDescriptor.DoSetNotification(const Value: Boolean);
var
B: TBytes;
begin
if not(TBluetoothProperty.Notify in FCharacteristic.Properties) then
raise EBluetoothLEDescriptorException.CreateFmt(SBluetoothCharacteristicError,
[GetEnumName(TypeInfo(TBluetoothProperty), Ord(TBluetoothProperty.Notify))]);
SetLength(
B,
2);
if Value then
B[0] := $01
else
B[0] := $00;
B[1] := $00;
SetValue(B);
end;
procedure TAndroidBluetoothGattDescriptor.DoSetUserDescription(const Value: string);
begin
SetValue(TEncoding.UTF8.GetBytes(Value));
end;
procedure TAndroidBluetoothGattDescriptor.DoSetValue(const AValue: TBytes);
begin
FJDescriptor.setValue(TBytesToTJavaArray(AValue));
end;
function TAndroidBluetoothGattDescriptor.GetUUID: TBluetoothUUID;
begin
Result := JUuidToBluetoothUuid(FJDescriptor.getUuid);
end;
{ TAndroidBluetoothLEManager }
constructor TAndroidBluetoothLEManager.Create;
var
LocalManager: JObject;
begin
inherited;
{ This code needs API_18 }
if not TOsVersion.Check(4, 3) then
raise EBluetoothManagerException.CreateFmt(SBluetoothAndroidVersionError, ['4.3', '18']);
FContext := TAndroidHelper.Context;
LocalManager := FContext.getSystemService(TJContext.JavaClass.BLUETOOTH_SERVICE);
if LocalManager <> nil then
FJManager := TJBluetoothManager.Wrap(LocalManager);
FScanSettings := TScanSettingsOptions.SCAN_MODE_BALANCED;
end;
destructor TAndroidBluetoothLEManager.Destroy;
begin
FJManager := nil;
FContext := nil;
FAdapter.Free;
inherited;
end;
function TAndroidBluetoothLEManager.DoGetGattServer: TBluetoothGattServer;
begin
{ Android KitKat 4.4.4 doesn't support Gatt servers, we need to wait until next release... }
if TOSVersion.Check(5) then
begin
Result := TAndroidBluetoothGattServer.Create(
Self,
TAndroidBluetoothLEAdapter(TBluetoothLEManager(Self).CurrentAdapter));
end
else
raise EBluetoothException.Create(SBluetoothNotImplemented);
end;
function TAndroidBluetoothLEManager.GetAdapterState: TBluetoothAdapterState;
begin
if FAdapter = nil then
FAdapter := TAndroidBluetoothLEAdapter.Create(
Self,
FJManager.getAdapter
);
Result := FAdapter.State;
end;
function TAndroidBluetoothLEManager.DoGetAdapter: TBluetoothLEAdapter;
var
LAndroidBluetoothLEAdapter: TAndroidBluetoothLEAdapter;
begin
if GetAdapterState = TBluetoothAdapterState.Off then
begin
LAndroidBluetoothLEAdapter := TAndroidBluetoothLEAdapter(FAdapter);
if LAndroidBluetoothLEAdapter.FRequestEnableCallback then
begin
InternalProcessMessages;
// The following work-around will only work if adapter is turned On
while
LAndroidBluetoothLEAdapter.FRequestEnableCallback and
(GetAdapterState <> TBluetoothAdapterState.On)
do
InternalProcessMessages;
LAndroidBluetoothLEAdapter.FRequestEnableCallback := false;
if GetAdapterState = TBluetoothAdapterState.Off then
FAdapter := nil;
end
else
FAdapter := nil;
end;
Result := FAdapter;
end;
function TAndroidBluetoothLEManager.DoGetSupportsGattClient: Boolean;
begin
Result := CheckOSVersionForGattClient;
end;
function TAndroidBluetoothLEManager.DoGetSupportsGattServer: Boolean;
begin
Result := CheckOSVersionForGattServer;
end;
function TAndroidBluetoothLEManager.GetConnectionState: TBluetoothConnectionState;
begin
if GetAdapterState = TBluetoothAdapterState.Off then
Result := TBluetoothConnectionState.Disconnected
else
Result := TBluetoothConnectionState.Connected;
end;
function TAndroidBluetoothLEManager.DoEnableBluetooth: Boolean;
begin
if GetConnectionState <> TBluetoothConnectionState.Disconnected then
Exit(true);
TAndroidBluetoothLEAdapter(FAdapter).FRequestEnableCallback := True;
TThread.CreateAnonymousThread(
procedure()
begin
var LIntent := TJIntent.JavaClass.init(TJBluetoothAdapter.JavaClass.ACTION_REQUEST_ENABLE);
TAndroidHelper.Activity.startActivityForResult(
LIntent,
REQUEST_ENABLE_BT
);
end
).Start();
Result := True;
end;
function TAndroidBluetoothLEManager.GetScanSettingsOptions: IScanSettingsOptions;
begin
if FIScanSettingsOptions = nil then
FIScanSettingsOptions := TScanSettingsOptions.Create;
Result := FIScanSettingsOptions;
end;
function TAndroidBluetoothLEManager.GetScanSettings: Integer;
begin
Result := FScanSettings;
end;
procedure TAndroidBluetoothLEManager.SetScanSettings(Value: Integer);
begin
FScanSettings := Value;
end;
{ TAndroidBluetoothSocket.TAndroidBluetoothSocketReader }
constructor TAndroidBluetoothSocket.TBluetoothSocketReader.Create(const ASocket: TAndroidBluetoothSocket; ABuffSize: Integer);
begin
inherited Create(True);
FSocket := ASocket;
FJBytes := TJavaArray.Create(ABuffSize);
end;
destructor TAndroidBluetoothSocket.TBluetoothSocketReader.Destroy;
begin
FDestroying := True;
FSocket.FReaderEvent.SetEvent;
inherited;
FSocket := nil;
FJBytes.Free;
end;
procedure TAndroidBluetoothSocket.TBluetoothSocketReader.Execute;
begin
inherited;
FBufferSize := 0;
FSocket.FSocketEvent.SetEvent;
while not Terminated and not FDestroying do
begin
FSocket.FReaderEvent.WaitFor(INFINITE);
FSocket.FReaderEvent.ResetEvent;
// Do Read
if not Terminated and not FDestroying and (FBufferSize = 0) then
begin
try
if FSocket.FJIStream <> nil then
FBufferSize := FSocket.FJIStream.read(FJBytes);
except
FBufferSize := -1;
end;
end;
// Inform Socket that there is readed data
if not FDestroying then
FSocket.FSocketEvent.SetEvent;
end;
end;
procedure TAndroidBluetoothSocket.TBluetoothSocketReader.GetBufferedData(const ABuffer: TBytes; AnOffset: Integer);
begin
if FBufferSize > 0 then
begin
Move(
PByte(FJBytes.Data)^,
ABuffer[AnOffset],
FBufferSize);
FBufferSize := 0;
end;
end;
function TAndroidBluetoothSocket.TBluetoothSocketReader.GetBufferedDataSize: Integer;
begin
Result := FBufferSize;
end;
{ TAndroidBluetoothAdvertiseListener }
constructor TAndroidBluetoothAdvertiseListener.Create(const AEvent: TEvent; AErrorCode: Integer);
begin
inherited Create;
FEvent := AEvent;
FErrorCode := AErrorCode;
end;
destructor TAndroidBluetoothAdvertiseListener.Destroy;
begin
inherited;
end;
procedure TAndroidBluetoothAdvertiseListener.onStartFailure(errorCode: Integer);
begin
FErrorCode := errorCode;
FEvent.SetEvent;
end;
procedure TAndroidBluetoothAdvertiseListener.onStartSuccess(settingsInEffect: JAdvertiseSettings);
begin
FEvent.SetEvent;
end;
{ TAndroidBluetoothLEAdvertiseData }
constructor TAndroidBluetoothLEAdvertiseData.Create(const ABluetoothGattServer: TBluetoothGattServer;
const AnAdapter: TBluetoothLEAdapter; const ADevice: TBluetoothLEDevice);
begin
inherited Create;
FBluetoothGattServer := ABluetoothGattServer;
FDevice := ADevice;
FAdapter := AnAdapter;
CreateAdvertiseDataJavaObjects;
end;
destructor TAndroidBluetoothLEAdvertiseData.Destroy;
begin
DoStopAdvertising;
FJAdvertiseData_Builder := nil;
FJScanResponse_Builder := nil;
FJScanResult := nil;
FJAdvertiseSettings_Builder := nil;
FJAdvertiseCallback := nil;
FJAdvertiseListener := nil;
inherited;
end;
procedure TAndroidBluetoothLEAdvertiseData.CreateAdvertiseDataJavaObjects;
begin
if TOSVersion.Check(5) and (FBluetoothGattServer <> nil) then
begin
if FJBluetoothLeAdvertiser = nil then
FJBluetoothLeAdvertiser := TAndroidBluetoothLEAdapter(FAdapter).FJAdapter.getBluetoothLeAdvertiser();
if FJBluetoothLeAdvertiser = nil then
raise EBluetoothLEAdvertiseDataException.Create(ADVERTISE_FAILED_DEVICE_NOT_SUPPORTED);
FJAdvertiseData_Builder := TJAdvertiseData_Builder.JavaClass.init;
FJAdvertiseData_Builder.setIncludeTxPowerLevel(False);
FJAdvertiseData_Builder.setIncludeDeviceName(True);
FJScanResponse_Builder := TJAdvertiseData_Builder.JavaClass.init;
FJScanResponse_Builder.setIncludeTxPowerLevel(False);
FJScanResponse_Builder.setIncludeDeviceName(False);
FJAdvertiseSettings_Builder := TJAdvertiseSettings_Builder.JavaClass.init;
FJAdvertiseSettings_Builder.setAdvertiseMode(TJAdvertiseSettings.JavaClass.ADVERTISE_MODE_BALANCED);
FJAdvertiseSettings_Builder.setTxPowerLevel(TJAdvertiseSettings.JavaClass.ADVERTISE_TX_POWER_HIGH);
FJAdvertiseSettings_Builder.setConnectable(True);
FEvent := TEvent.Create;
FJAdvertiseListener := TAndroidBluetoothAdvertiseListener.Create(
FEvent,
FErrorCode);
FJAdvertiseCallback := TJRTLAdvertiseCallback.JavaClass.init(FJAdvertiseListener);
end;
end;
procedure TAndroidBluetoothLEAdvertiseData.DoStartAdvertising;
const
FLAGS_FIELD_BYTES = 3;
UUID_BYTES_16_BIT = 2;
UUID_BYTES_32_BIT = 4;
UUID_BYTES_128_BIT = 16;
OVERHEAD_BYTES_PER_FIELD = 2;
SERVICE_DATA_UUID_LENGTH = 2;
MANUFACTURER_SPECIFIC_DATA_LENGTH = 2;
MAX_ADVDATA_LENGTH = 31;
BEACON_ST_TYPE: Word = $0215;
var
I: Integer;
LAdvertiseService: Boolean;
LNameInAdvData: Boolean;
AdvertiseDataFull: Boolean;
LAdvertiseDataLength: Integer;
FScanResponseDataLength: Integer;
ItemLength: Integer;
OVERHEAD16Bit: Integer;
OVERHEAD128Bit: Integer;
LManufacturerId: Word;
LManufacturerSpecificData: TBytes;
Error: string;
begin
FJBluetoothLeAdvertiser.stopAdvertising(FJAdvertiseCallback);
FJAdvertiseCallback.setListener(nil);
FJAdvertiseData_Builder := nil;
FJScanResponse_Builder := nil;
FJAdvertiseSettings_Builder := nil;
FJAdvertiseCallback := nil;
FJAdvertiseListener := nil;
LAdvertiseService := FBluetoothGattServer.AdvertiseService;
CreateAdvertiseDataJavaObjects;
FBluetoothGattServer.AdvertiseService := False;
LAdvertiseDataLength := Length(FManufacturerSpecificData);
FScanResponseDataLength := 0;
OVERHEAD16Bit := 0;
OVERHEAD128Bit := 0;
LNameInAdvData := True;
if LAdvertiseDataLength > 0 then
begin
LManufacturerId := PWord(@FManufacturerSpecificData[0])^;
SetLength(
LManufacturerSpecificData,
Length(FManufacturerSpecificData) - LManufacturerId.Size);
Move(
FManufacturerSpecificData[LManufacturerId.Size],
LManufacturerSpecificData[0],
Length(LManufacturerSpecificData));
FJAdvertiseData_Builder.addManufacturerData(
LManufacturerId,
TBytesToTJavaArray(LManufacturerSpecificData));
LAdvertiseDataLength := OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + LAdvertiseDataLength;
if LAdvertiseDataLength + FLAGS_FIELD_BYTES + OVERHEAD_BYTES_PER_FIELD + FAdapter.AdapterName.Length > MAX_ADVDATA_LENGTH then
begin
FJAdvertiseData_Builder.setIncludeDeviceName(False);
FJScanResponse_Builder.setIncludeDeviceName(True);
FScanResponseDataLength := OVERHEAD_BYTES_PER_FIELD + FAdapter.AdapterName.Length;
LNameInAdvData := False;
end;
end;
LAdvertiseDataLength := LAdvertiseDataLength + FLAGS_FIELD_BYTES;
for I := 0 to FServiceData.Count - 1 do
begin
ItemLength := Length(FServiceData.ToArray[I].Value);
if LAdvertiseDataLength + ItemLength + OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH <= MAX_ADVDATA_LENGTH then
begin
FJAdvertiseData_Builder.addServiceData(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(FServiceData.ToArray[I].Key)),
TBytesToTJavaArray(FServiceData.ToArray[I].Value));
LAdvertiseDataLength := LAdvertiseDataLength + ItemLength + OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH;
if LNameInAdvData and (LAdvertiseDataLength + OVERHEAD_BYTES_PER_FIELD + FAdapter.AdapterName.Length > MAX_ADVDATA_LENGTH) then
begin
FJAdvertiseData_Builder.setIncludeDeviceName(False);
FJScanResponse_Builder.setIncludeDeviceName(True);
FScanResponseDataLength := FScanResponseDataLength + OVERHEAD_BYTES_PER_FIELD + FAdapter.AdapterName.Length;
LNameInAdvData := False;
end;
end
else
begin
if FScanResponseDataLength + ItemLength + OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH <= MAX_ADVDATA_LENGTH then
begin
FJScanResponse_Builder.addServiceData(
TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(FServiceData.ToArray[I].Key)),
TBytesToTJavaArray(FServiceData.ToArray[I].Value));
FScanResponseDataLength := FScanResponseDataLength + ItemLength + OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH;
end;
end;
end;
AdvertiseDataFull := False;
if LNameInAdvData then
LAdvertiseDataLength := LAdvertiseDataLength + OVERHEAD_BYTES_PER_FIELD + FAdapter.AdapterName.Length;
for I := 0 to FServiceUUIDs.Count - 1 do
begin
if TBluetoothUUIDHelper.IsBluetoothBaseUUIDBased(FServiceUUIDs.Items[I]) then
begin
OVERHEAD16Bit := OVERHEAD_BYTES_PER_FIELD;
ItemLength := UUID_BYTES_16_BIT;
end
else
begin
OVERHEAD128Bit := OVERHEAD_BYTES_PER_FIELD;
ItemLength := UUID_BYTES_128_BIT;
end;
if not AdvertiseDataFull then
if (not AdvertiseDataFull) and (LAdvertiseDataLength + ItemLength + OVERHEAD16Bit + OVERHEAD128Bit <= MAX_ADVDATA_LENGTH) then
begin
FJAdvertiseData_Builder.addServiceUuid(TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(FServiceUUIDs.Items[I])));
LAdvertiseDataLength := LAdvertiseDataLength + ItemLength;
end
else
begin
AdvertiseDataFull := True;
if ItemLength = UUID_BYTES_16_BIT then
begin
OVERHEAD16Bit := OVERHEAD_BYTES_PER_FIELD;
OVERHEAD128Bit := 0;
end
else
begin
OVERHEAD16Bit := 0;
OVERHEAD128Bit := OVERHEAD_BYTES_PER_FIELD;
end
end;
if AdvertiseDataFull then
if (FScanResponseDataLength + ItemLength + OVERHEAD16Bit + OVERHEAD128Bit) <= MAX_ADVDATA_LENGTH then
begin
FJScanResponse_Builder.addServiceUuid(TJParcelUuid.JavaClass.init(BluetoothUuidToJUuid(FServiceUUIDs.Items[I])));
FScanResponseDataLength := FScanResponseDataLength + ItemLength;
end;
end;
FBluetoothGattServer.AdvertiseService := LAdvertiseService;
if FJAdvertiseCallback <> nil then
begin
FJAdvertiseData := FJAdvertiseData_Builder.build;
FJScanResponse := FJScanResponse_Builder.build;
FErrorCode := 0;
FEvent.ResetEvent;
FJBluetoothLeAdvertiser.startAdvertising(
FJAdvertiseSettings_Builder.build,
FJAdvertiseData,
FJScanResponse,
FJAdvertiseCallback);
FEvent.WaitFor(2000);
if FErrorCode > 0 then
begin
FAdvertising := False;
if FErrorCode = TJAdvertiseCallback.JavaClass.ADVERTISE_FAILED_DATA_TOO_LARGE then
Error := ADVERTISE_FAILED_DATA_TOO_LARGE
else
if FErrorCode = TJAdvertiseCallback.JavaClass.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS then
Error := ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
else
if FErrorCode = TJAdvertiseCallback.JavaClass.ADVERTISE_FAILED_ALREADY_STARTED then
Error := ADVERTISE_FAILED_ALREADY_STARTED
else
if FErrorCode = TJAdvertiseCallback.JavaClass.ADVERTISE_FAILED_INTERNAL_ERROR then
Error := ADVERTISE_FAILED_INTERNAL_ERROR
else
if FErrorCode = TJAdvertiseCallback.JavaClass.ADVERTISE_FAILED_FEATURE_UNSUPPORTED then
Error := ADVERTISE_FAILED_FEATURE_UNSUPPORTED
else
Error := ADVERTISE_FAILED_UNKNOWN_ERROR;
raise EBluetoothLEAdvertiseDataException.Create(Error);
end
else
FAdvertising := True;
end;
end;
procedure TAndroidBluetoothLEAdvertiseData.DoStopAdvertising;
begin
if FAdvertising then
begin
FJBluetoothLeAdvertiser.stopAdvertising(FJAdvertiseCallback);
FAdvertising := False;
end;
end;
function TAndroidBluetoothLEAdvertiseData.GetDataForService(
const AServiceUUID: TBluetoothUUID): TBytes;
begin
if Length(GetServiceData) > 0 then
FServiceData.TryGetValue(
AServiceUUID,
Result)
else
Result := nil;
end;
function TAndroidBluetoothLEAdvertiseData.GetLocalName: string;
begin
if FDevice = nil then
begin
if FBluetoothGattServer <> nil then
Flocalname := FBluetoothGattServer.GattServerName
end
else
if (FDevice.AdvertisedData <> nil) and (FDevice.AdvertisedData.ContainsKey(TScanResponseKey.CompleteLocalName)) then
begin
Result := TEncoding.UTF8.GetString(FDevice.AdvertisedData.Items[TScanResponseKey.CompleteLocalName]);
if Result <> '' then
FLocalName := Result;
end
else
Flocalname := FDevice.DeviceName;
Result := Flocalname;
end;
function TAndroidBluetoothLEAdvertiseData.GetManufacturerSpecificData: TBytes;
begin
if (FDevice <> nil) and (FDevice.AdvertisedData <> nil) then
if FDevice.AdvertisedData.ContainsKey(TScanResponseKey.ManufacturerSpecificData) then
FManufacturerSpecificData := FDevice.AdvertisedData.Items[TScanResponseKey.ManufacturerSpecificData];
Result := FManufacturerSpecificData;
end;
function TAndroidBluetoothLEAdvertiseData.GetServiceData: TArray;
var
LData: TBytes;
LServiceTBytes: TBytes;
LServiceUUID: TGUID;
LSize: Integer;
LServiceData: TPair<TBluetoothUUID, TBytes>;
begin
if (FDevice <> nil) and (FDevice.AdvertisedData <> nil) then
if FDevice.AdvertisedData.ContainsKey(TScanResponseKey.ServiceData) then
begin
LData := FDevice.AdvertisedData.Items[TScanResponseKey.ServiceData];
LServiceUUID := ServiceUUIDToGUID([LData[1], LData[0]]);
LSize := Length(LData) - 2;
SetLength(
LServiceTBytes,
LSize);
Move(
LData[2],
LServiceTBytes[0],
LSize);
FServiceData.AddOrSetValue(
LServiceUUID,
LServiceTBytes);
end;
// Prepared to be an array, but it will just own one element for now.
SetLength(
Result,
FServiceData.count);
LSize := 0;
for LServiceData in FServiceData do
begin
Result[LSize].create(LServiceData);
Inc(LSize);
end;
end;
function TAndroidBluetoothLEAdvertiseData.GetServiceUUIDs: TArray;
function ScanResponseToAdvertiseData(const ScanResponse: TScanResponse):TArray;
type
TServicesLengthType = (S16B = 2, S32B = 4, S128B = 16);
procedure ChekBLEServices(const AData: TBytes; AServicesLengthType: TServicesLengthType);
var
LDataLength: Integer;
I: Integer;
Position: Integer;
LDeviation: Integer;
begin
LDeviation := 0;
Position := Length(Result);
if AServicesLengthType = TServicesLengthType.S128B then
begin
SetLength(
Result,
Position + 1);
Result[0] := ServiceUUIDToGUID(AData); // we just can have one Service in 128 bits format
end
else
begin
LDataLength := Length(AData);
I := 0;
while I < LDataLength do
begin
SetLength(
Result,
Position + 1);
Result[Position] := ServiceUUIDToGUID([AData[I + LDeviation + 1], AData[I + LDeviation]]);
Inc(
I,
Integer(AServicesLengthType));
Inc(Position);
end;
end;
end;
begin
if ScanResponse.ContainsKey(TScanResponseKey.IncompleteList128SCUUID) then
ChekBLEServices(
ScanResponse.Items[TScanResponseKey.IncompleteList128SCUUID],
TServicesLengthType.S128B);
if ScanResponse.ContainsKey(TScanResponseKey.CompleteList128SCUUID) then
ChekBLEServices(
ScanResponse.Items[TScanResponseKey.CompleteList128SCUUID],
TServicesLengthType.S128B);
if ScanResponse.ContainsKey(TScanResponseKey.IncompleteList16SCUUID) then
ChekBLEServices(
ScanResponse.Items[TScanResponseKey.IncompleteList16SCUUID],
TServicesLengthType.S16B);
if ScanResponse.ContainsKey(TScanResponseKey.CompleteList16SCUUID) then
ChekBLEServices(
ScanResponse.Items[TScanResponseKey.CompleteList16SCUUID],
TServicesLengthType.S16B);
if ScanResponse.ContainsKey(TScanResponseKey.IncompleteList32SCUUID) then
ChekBLEServices(
ScanResponse.Items[TScanResponseKey.IncompleteList32SCUUID],
TServicesLengthType.S32B);
if ScanResponse.ContainsKey(TScanResponseKey.CompleteList32SCUUID) then
ChekBLEServices(
ScanResponse.Items[TScanResponseKey.CompleteList32SCUUID],
TServicesLengthType.S32B);
end;
begin
if FDevice = nil then
Result := FServiceUUIDs.ToArray
else
Result := ScanResponseToAdvertiseData(FDevice.AdvertisedData);
end;
function TAndroidBluetoothLEAdvertiseData.GetTxPowerLevel: Integer;
begin
if (FDevice <> nil) and (FDevice.AdvertisedData <> nil) then
if FDevice.AdvertisedData.ContainsKey(TScanResponseKey.TxPowerLevel) then
FTxPowerLevel := ShortInt(FDevice.AdvertisedData.Items[TScanResponseKey.TxPowerLevel]);
Result := FTxPowerLevel;
end;
procedure TAndroidBluetoothLEAdvertiseData.SetLocalName(
const ALocalName: string);
begin
// In Android we cannot set a GattSerever name that is different from the one of the device.
// raise EBluetoothADataException.CreateFmt(SBluetoothNotSupported, ['SetLocalName', 'Android']); // Do not translate.
end;
procedure TAndroidBluetoothLEAdvertiseData.SetManufacturerSpecificData(
const AManufacturerSpecificData: TBytes);
begin
if TOSVersion.Check(5) then
FManufacturerSpecificData := AManufacturerSpecificData
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
procedure TAndroidBluetoothLEAdvertiseData.SetTxPowerLevel(
ATxPowerLevel: Integer);
begin
if TOSVersion.Check(5) then
begin
FTxPowerLevel := ATxPowerLevel;
end
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
function TAndroidBluetoothLEAdvertiseData.DoAddServiceData(
const AServiceUUID: TBluetoothUUID; const AData: TBytes): Boolean;
begin
if TOSVersion.Check(5) then
begin
Result := True;
end
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
function TAndroidBluetoothLEAdvertiseData.DoAddServiceUUID(const AServiceUUID: TBluetoothUUID): Boolean;
begin
if TOSVersion.Check(5) then
Result := True
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
procedure TAndroidBluetoothLEAdvertiseData.DoRemoveServiceUUID(
const AServiceUUID: TBluetoothUUID);
begin
if TOSVersion.Check(5) then
FServiceUUIDChanged := True
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
procedure TAndroidBluetoothLEAdvertiseData.DoRemoveServiceData(
const AServiceUUID: TBluetoothUUID);
begin
if TOSVersion.Check(5) then
FServiceDataChanged := True
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
procedure TAndroidBluetoothLEAdvertiseData.DoClearServiceData;
begin
if TOSVersion.Check(5) then
FServiceDataChanged := True
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
procedure TAndroidBluetoothLEAdvertiseData.DoClearServiceUUIDs;
begin
if TOSVersion.Check(5) then
FServiceUUIDChanged := True
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
function TAndroidBluetoothLEAdvertiseData.ContainsServiceUUID(
const AServiceUUID: TBluetoothUUID): Boolean;
var
LServiceArray: TArray;
I: Integer;
begin
if TOSVersion.Check(5) then
begin
Result := False;
if (FDevice <> nil) and (FDevice.AdvertisedData <> nil) then
begin
LServiceArray := GetServiceUUIDs;
for I := 0 to Length(LServiceArray) - 1 do
if LServiceArray[I] = AServiceUUID then
Result := True
end;
end
else
raise EBluetoothLEAdvertiseDataException.CreateFmt(SBluetoothAndroidVersionError, ['5', '21']); // Do not translate.
end;
{ TScanCallback }
constructor TScanCallback.Create(const AnAdapter: TAndroidBluetoothLEAdapter);
begin
inherited Create;
FAdapter := AnAdapter;
end;
procedure TScanCallback.onBatchScanResults(results: JList);
begin
ES_BTAPI_TRACE('TScanCallback.onBatchScanResults');
end;
procedure TScanCallback.onScanFailed(errorCode: Integer);
begin
ES_BTAPI_TRACE(
'TScanCallback.onScanFailed(errorCode: 0x%0X)',
[errorCode]
);
// SCAN_FAILED_ALREADY_STARTED $01
// SCAN_FAILED_APPLICATION_REGISTRATION_FAILED $02
// SCAN_FAILED_INTERNAL_ERROR $03
// SCAN_FAILED_FEATURE_UNSUPPORTED $04
if FAdapter.isScanning then
begin
FAdapter.FStartScanFailed := errorCode;
FAdapter.DoDiscoveryEnd(
FAdapter,
nil
);
end;
end;
function TBytesToHexString(const AValue: TBytes): string;
var
I:Integer;
begin
Result := '';
for I := 0 to Length(AValue) - 1 do
Result := Result + AValue[I].ToHexString(2);
end;
procedure TScanCallback.onScanResult(callbackType: Integer; result: Jle_ScanResult);
begin
ES_BTAPI_TRACE(
'TScanCallback.onScanResult(callbackType: %d)',
[callbackType]
);
FAdapter.FStartScanFailed := 0;
var LNew := False;
var LDevice := TAndroidBluetoothLEDevice(
TAndroidBluetoothLEManager.GetDeviceInList(
JStringToString(result.getDevice.getAddress),
FAdapter.FManager.AllDiscoveredDevices)
);
if not Assigned(LDevice) then
begin
LDevice := TAndroidBluetoothLEDevice.Create(
result.getDevice,
False,
FAdapter.FManager.ForceRefreshCachedDevices
);
LNew := True;
end else
LDevice.FJDevice := result.getDevice;
var LScanRecordBytes := result.getScanRecord.getBytes;
try
LDevice.FAdvertisedData := ScanRecordToTScanResponse(
LScanRecordBytes,
LDevice.FAdvertisedData
);
finally
LScanRecordBytes.Free;
end;
LDevice.FLastRSSI := result.getRssi;
FAdapter.DoDeviceDiscovered(
LDevice,
LNew,
FAdapter.m_scanFilterList
);
end;
{ ScanSettingsOptions }
function TScanSettingsOptions.GetCALLBACK_TYPE_ALL_MATCHES: Integer;
begin
Result := CALLBACK_TYPE_ALL_MATCHES;
end;
function TScanSettingsOptions.GetCALLBACK_TYPE_FIRST_MATCH: Integer;
begin
Result := CALLBACK_TYPE_FIRST_MATCH;
end;
function TScanSettingsOptions.GetCALLBACK_TYPE_MATCH_LOST: Integer;
begin
Result := CALLBACK_TYPE_MATCH_LOST;
end;
function TScanSettingsOptions.GetMATCH_MODE_AGGRESSIVE: Integer;
begin
Result := MATCH_MODE_AGGRESSIVE;
end;
function TScanSettingsOptions.GetMATCH_MODE_STICKY: Integer;
begin
Result := MATCH_MODE_STICKY;
end;
function TScanSettingsOptions.GetMATCH_NUM_FEW_ADVERTISEMENT: Integer;
begin
Result := MATCH_NUM_FEW_ADVERTISEMENT;
end;
function TScanSettingsOptions.GetMATCH_NUM_MAX_ADVERTISEMENT: Integer;
begin
Result := MATCH_NUM_MAX_ADVERTISEMENT;
end;
function TScanSettingsOptions.GetMATCH_NUM_ONE_ADVERTISEMENT: Integer;
begin
Result := MATCH_NUM_ONE_ADVERTISEMENT;
end;
function TScanSettingsOptions.GetSCAN_MODE_BALANCED: Integer;
begin
Result := SCAN_MODE_BALANCED;
end;
function TScanSettingsOptions.GetSCAN_MODE_LOW_LATENCY: Integer;
begin
Result := SCAN_MODE_LOW_LATENCY;
end;
function TScanSettingsOptions.GetSCAN_MODE_LOW_POWER: Integer;
begin
Result := SCAN_MODE_LOW_POWER;
end;
function TScanSettingsOptions.GetSCAN_MODE_OPPORTUNISTIC: Integer;
begin
Result := SCAN_MODE_OPPORTUNISTIC;
end;
{$ENDIF}
end.
Delphi 11.2 BLE for Android
- Информация о материале
- Автор: Vsevolod V Gromov
- Категория: Resources
- Просмотров: 182
A number of fixes for Delphi 10.2 RTL BLE for Android, avoiding tedious main thread locks, and Android BLE descriptors leaks, consequently causing BLE stack to stop working, unless hardware BLE being toggled explicitly.