diff --git a/Source/Plugin.BLE.Abstractions/AdapterBase.cs b/Source/Plugin.BLE.Abstractions/AdapterBase.cs index b7fc6969..25cbe0e8 100644 --- a/Source/Plugin.BLE.Abstractions/AdapterBase.cs +++ b/Source/Plugin.BLE.Abstractions/AdapterBase.cs @@ -29,12 +29,13 @@ public abstract class AdapterBase : IAdapter public bool IsScanning { - get { return _isScanning; } - private set { _isScanning = value; } + get => _isScanning; + private set => _isScanning = value; } public int ScanTimeout { get; set; } = 10000; - public ScanMode ScanMode { get; set; } = ScanMode.LowPower; + + public ScanMode ScanMode { get; set; } = ScanMode.Balanced; public virtual IList DiscoveredDevices => _discoveredDevices; diff --git a/Source/Plugin.BLE.Abstractions/CharacteristicBase.cs b/Source/Plugin.BLE.Abstractions/CharacteristicBase.cs index 97430836..56bb5693 100644 --- a/Source/Plugin.BLE.Abstractions/CharacteristicBase.cs +++ b/Source/Plugin.BLE.Abstractions/CharacteristicBase.cs @@ -25,7 +25,7 @@ public abstract class CharacteristicBase : ICharacteristic public CharacteristicWriteType WriteType { - get { return _writeType; } + get => _writeType; set { if (value == CharacteristicWriteType.WithResponse && !Properties.HasFlag(CharacteristicPropertyType.Write) || @@ -65,31 +65,55 @@ protected CharacteristicBase(IService service) public async Task ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) { - if (!CanRead) + try { - throw new InvalidOperationException("Characteristic does not support read."); - } + if (!CanRead) + { + throw new InvalidOperationException("Characteristic does not support read."); + } - Trace.Message("Characteristic.ReadAsync"); - return await ReadNativeAsync(cancellationToken); + Trace.Message("Characteristic.ReadAsync"); + return await ReadNativeAsync(cancellationToken); + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message); + } + finally + { + //notify listener + //operationManager.opreation completed + } } public async Task WriteAsync(byte[] data, CancellationToken cancellationToken = default(CancellationToken)) { - if (data == null) + try { - throw new ArgumentNullException(nameof(data)); - } + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } - if (!CanWrite) - { - throw new InvalidOperationException("Characteristic does not support write."); - } + if (!CanWrite) + { + throw new InvalidOperationException("Characteristic does not support write."); + } - var writeType = GetWriteType(); + var writeType = GetWriteType(); - Trace.Message("Characteristic.WriteAsync"); - return await WriteNativeAsync(data, writeType, cancellationToken); + Trace.Message("Characteristic.WriteAsync"); + return await WriteNativeAsync(data, writeType, cancellationToken); + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message); + } + finally + { + //notify listener + //operationManager.opreation completed + } } private CharacteristicWriteType GetWriteType() diff --git a/Source/Plugin.BLE.Abstractions/ConnectParameter.cs b/Source/Plugin.BLE.Abstractions/ConnectParameter.cs index ec96ac22..2a52ecf1 100644 --- a/Source/Plugin.BLE.Abstractions/ConnectParameter.cs +++ b/Source/Plugin.BLE.Abstractions/ConnectParameter.cs @@ -6,7 +6,7 @@ public struct ConnectParameters { /// - /// Android only, from documnetation: + /// Android only, from documentation: /// boolean: Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote device becomes available (true). /// public bool AutoConnect { get; } @@ -26,6 +26,7 @@ public struct ConnectParameters /// Android only: For Dual Mode device, force transport mode to LE. The default is false. public ConnectParameters(bool autoConnect = false, bool forceBleTransport = false) { + //we should analyze when this auto connect should actually be set to true AutoConnect = autoConnect; ForceBleTransport = forceBleTransport; } diff --git a/Source/Plugin.BLE.Android/Adapter.cs b/Source/Plugin.BLE.Android/Adapter.cs index 374ceeb9..8c854c80 100644 --- a/Source/Plugin.BLE.Android/Adapter.cs +++ b/Source/Plugin.BLE.Android/Adapter.cs @@ -109,10 +109,12 @@ private void StartScanningNew(Guid[] serviceUuids) scanFilters.Add(sfb.Build()); } } - - var ssb = new ScanSettings.Builder(); - ssb.SetScanMode(ScanMode.ToNative()); - //ssb.SetCallbackType(ScanCallbackType.AllMatches); + var ssb = new ScanSettings.Builder() + .SetScanMode(ScanMode.ToNative()) + .SetCallbackType(ScanCallbackType.AllMatches) + .SetMatchMode(BluetoothScanMatchMode.Aggressive) + .SetNumOfMatches((int)BluetoothScanMatchNumber.FewAdvertisement) + .SetReportDelay(0); if (_bluetoothAdapter.BluetoothLeScanner != null) { @@ -162,6 +164,12 @@ protected override void DisconnectDeviceNative(IDevice device) { var macBytes = deviceGuid.ToByteArray().Skip(10).Take(6).ToArray(); var nativeDevice = _bluetoothAdapter.GetRemoteDevice(macBytes); + if (nativeDevice.Type == BluetoothDeviceType.Unknown )//nativeDevice.GetType() == BluetoothDevice.DeviceTypeUnknown) + { + //the peripheral is not cached + //should trigger a new scan + Trace.Message($"Caution: the peripheral is not cached should trigger a new scan Android.{nativeDevice.Name} mac:{macBytes}"); + } var device = new Device(this, nativeDevice, null, 0, new byte[] { }); diff --git a/Source/Plugin.BLE.Android/AndroidCrossBluetoothLE.cs b/Source/Plugin.BLE.Android/AndroidCrossBluetoothLE.cs index daf4883a..46e505f2 100644 --- a/Source/Plugin.BLE.Android/AndroidCrossBluetoothLE.cs +++ b/Source/Plugin.BLE.Android/AndroidCrossBluetoothLE.cs @@ -4,7 +4,7 @@ namespace Plugin.BLE { /// - /// Cross platform bluetooth LE implemenation. + /// Cross platform bluetooth LE implementation. /// public class AndroidCrossBluetoothLE : ICrossBluetoothLE { diff --git a/Source/Plugin.BLE.Android/BleImplementation.cs b/Source/Plugin.BLE.Android/BleImplementation.cs index 8f648d1a..8a76c8eb 100644 --- a/Source/Plugin.BLE.Android/BleImplementation.cs +++ b/Source/Plugin.BLE.Android/BleImplementation.cs @@ -1,9 +1,11 @@ +// Copyright (C) 2020 Bang & Olufsen A/S - All Rights Reserved using Android.App; using Android.Bluetooth; using Android.Content; using Android.Content.PM; using Plugin.BLE.Abstractions; using Plugin.BLE.Abstractions.Contracts; +using Plugin.BLE.Abstractions.EventArgs; using Plugin.BLE.BroadcastReceivers; using Plugin.BLE.Extensions; using Adapter = Plugin.BLE.Android.Adapter; @@ -14,23 +16,41 @@ namespace Plugin.BLE internal class BleImplementation : BleImplementationBase { private BluetoothManager _bluetoothManager; + private BluetoothStatusBroadcastReceiver _statusBroadcastReceiver; + private BondStatusBroadcastReceiver _bondStatusBroadcastReceiver; protected override void InitializeNative() { DefaultTrace.DefaultTraceInit(); var ctx = Application.Context; - if (!ctx.PackageManager.HasSystemFeature(PackageManager.FeatureBluetoothLe)) + if (ctx.PackageManager.HasSystemFeature(PackageManager.FeatureBluetoothLe) == false) + { return; + } - var statusChangeReceiver = new BluetoothStatusBroadcastReceiver(UpdateState); - ctx.RegisterReceiver(statusChangeReceiver, new IntentFilter(BluetoothAdapter.ActionStateChanged)); + _statusBroadcastReceiver = new BluetoothStatusBroadcastReceiver(UpdateState); + ctx.RegisterReceiver(_statusBroadcastReceiver, new IntentFilter(BluetoothAdapter.ActionStateChanged)); + + _bondStatusBroadcastReceiver = new BondStatusBroadcastReceiver(); + _bondStatusBroadcastReceiver.BondStateChanged += UpdateBondState; + ctx.RegisterReceiver(_bondStatusBroadcastReceiver, new IntentFilter(BluetoothAdapter.ActionStateChanged)); _bluetoothManager = (BluetoothManager)ctx.GetSystemService(Context.BluetoothService); } + public void close() + { + _bondStatusBroadcastReceiver.BondStateChanged -= UpdateBondState; + if (Application.Context != null) + { + Application.Context.UnregisterReceiver(_statusBroadcastReceiver); + Application.Context.UnregisterReceiver(_bondStatusBroadcastReceiver); + } + } + protected override BluetoothState GetInitialStateNative() { - if(_bluetoothManager == null) + if (_bluetoothManager == null) return BluetoothState.Unavailable; return _bluetoothManager.Adapter.State.ToBluetoothState(); @@ -45,5 +65,9 @@ private void UpdateState(BluetoothState state) { State = state; } + + private void UpdateBondState(object sender, DeviceBondStateChangedEventArgs state) + { + } } } \ No newline at end of file diff --git a/Source/Plugin.BLE.Android/CallbackEventArgs/DescriptorCallbackEventArgs.cs b/Source/Plugin.BLE.Android/CallbackEventArgs/DescriptorCallbackEventArgs.cs index f2d1b9fb..48b2a84c 100644 --- a/Source/Plugin.BLE.Android/CallbackEventArgs/DescriptorCallbackEventArgs.cs +++ b/Source/Plugin.BLE.Android/CallbackEventArgs/DescriptorCallbackEventArgs.cs @@ -1,6 +1,6 @@ using System; using Android.Bluetooth; -using Plugin.BLE.Abstractions.Exceptions; + namespace Plugin.BLE.Android.CallbackEventArgs { public class DescriptorCallbackEventArgs diff --git a/Source/Plugin.BLE.Android/CallbackEventArgs/MtuRequestCallbackEventArgs.cs b/Source/Plugin.BLE.Android/CallbackEventArgs/MtuRequestCallbackEventArgs.cs index 012b5a67..2f778226 100644 --- a/Source/Plugin.BLE.Android/CallbackEventArgs/MtuRequestCallbackEventArgs.cs +++ b/Source/Plugin.BLE.Android/CallbackEventArgs/MtuRequestCallbackEventArgs.cs @@ -1,5 +1,4 @@ using System; -using Plugin.BLE.Abstractions.Contracts; namespace Plugin.BLE.Android.CallbackEventArgs { diff --git a/Source/Plugin.BLE.Android/CallbackEventArgs/RssiReadCallbackEventArgs.cs b/Source/Plugin.BLE.Android/CallbackEventArgs/RssiReadCallbackEventArgs.cs index c510af3d..ec5586dd 100644 --- a/Source/Plugin.BLE.Android/CallbackEventArgs/RssiReadCallbackEventArgs.cs +++ b/Source/Plugin.BLE.Android/CallbackEventArgs/RssiReadCallbackEventArgs.cs @@ -1,6 +1,4 @@ using System; -using Android.Bluetooth; -using Plugin.BLE.Abstractions.Contracts; namespace Plugin.BLE.Android.CallbackEventArgs { diff --git a/Source/Plugin.BLE.Android/CrazyQueue.cs b/Source/Plugin.BLE.Android/CrazyQueue.cs index 5fc175b3..63bf27ba 100644 --- a/Source/Plugin.BLE.Android/CrazyQueue.cs +++ b/Source/Plugin.BLE.Android/CrazyQueue.cs @@ -67,7 +67,7 @@ private static async Task InnerRun() Task task; lock (_queueLock) { - if (_queue.Count == 0) + if (_queue.Count == 0 || _queue.Peek() == null) { _isRunning = 0; return; @@ -82,4 +82,4 @@ private static async Task InnerRun() var _ = InnerRun(); } } -} +} \ No newline at end of file diff --git a/Source/Plugin.BLE.Android/Descriptor.cs b/Source/Plugin.BLE.Android/Descriptor.cs index 9ecdb852..73b5453d 100644 --- a/Source/Plugin.BLE.Android/Descriptor.cs +++ b/Source/Plugin.BLE.Android/Descriptor.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Android.Bluetooth; +using Java.Lang.Reflect; using Plugin.BLE.Abstractions; using Plugin.BLE.Abstractions.Contracts; using Plugin.BLE.Abstractions.Utils; @@ -95,5 +96,20 @@ private void ReadInternal() if (!_gatt.ReadDescriptor(_nativeDescriptor)) throw new Exception("GATT: read characteristic FALSE"); } + + private bool ClearServicesCache() + { + bool result = false; + try { + Method refreshMethod = _gatt.Class.GetMethod("refresh"); + if(refreshMethod != null) { + result = (bool) refreshMethod.Invoke(_gatt); + } + } catch (Exception e) { + throw new Exception("GATT: Could not invoke refresh method"); + } + return result; + } + } } \ No newline at end of file diff --git a/Source/Plugin.BLE.Android/Device.cs b/Source/Plugin.BLE.Android/Device.cs index 2c4673c7..36adc229 100644 --- a/Source/Plugin.BLE.Android/Device.cs +++ b/Source/Plugin.BLE.Android/Device.cs @@ -62,7 +62,10 @@ protected override async Task> GetServicesNativeAsync() } return await TaskBuilder.FromEvent, EventHandler, EventHandler>( - execute: () => _gatt.DiscoverServices(), + execute: () => + { + _gatt.DiscoverServices(); + }, getCompleteHandler: (complete, reject) => ((sender, args) => { complete(_gatt.Services.Select(service => new Service(service, _gatt, _gattCallback, this))); diff --git a/Source/Plugin.BLE.Android/Extensions/ScanModeExtension.cs b/Source/Plugin.BLE.Android/Extensions/ScanModeExtension.cs index efd82085..b30278e6 100644 --- a/Source/Plugin.BLE.Android/Extensions/ScanModeExtension.cs +++ b/Source/Plugin.BLE.Android/Extensions/ScanModeExtension.cs @@ -13,9 +13,6 @@ internal static class ScanModeExtension { public static AndroidScanMode ToNative(this ScanMode scanMode) { - if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop) - throw new InvalidOperationException("Scan modes are not implemented in API lvl < 21."); - switch (scanMode) { case ScanMode.Passive: diff --git a/Source/Plugin.BLE.Android/GattCallback.cs b/Source/Plugin.BLE.Android/GattCallback.cs index 447b4681..9fe9a12a 100644 --- a/Source/Plugin.BLE.Android/GattCallback.cs +++ b/Source/Plugin.BLE.Android/GattCallback.cs @@ -111,8 +111,7 @@ public override void OnConnectionStateChange(BluetoothGatt gatt, GattStatus stat // connected case ProfileState.Connected: Trace.Message("Connected"); - - //Check if the operation was requested by the user + //Check if the operation was requested by the user if (_device.IsOperationRequested) { _device.Update(gatt.Device, gatt); @@ -145,6 +144,42 @@ public override void OnConnectionStateChange(BluetoothGatt gatt, GattStatus stat return; } + switch (_device.BluetoothDevice.BondState) + { + case Bond.Bonded: + case Bond.None: + // Connected to device, now proceed to discover it's services but delay a bit if needed + // int delayWhenBonded = 0; + // if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { + // delayWhenBonded = 1000; + // } + // final int delay = bondstate == BOND_BONDED ? delayWhenBonded : 0; + // discoverServicesRunnable = new Runnable() { + // @Override + // public void run() { + // Log.d(TAG, String.format(Locale.ENGLISH, "discovering services of '%s' with delay of %d ms", getName(), delay)); + // boolean result = gatt.discoverServices(); + // if (!result) { + // Log.e(TAG, "discoverServices failed to start"); + // } + // discoverServicesRunnable = null; + // } + // }; + // bleHandler.postDelayed(discoverServicesRunnable, delay); + break; + case Bond.Bonding: + //should wait till bonded + // Bonding process in progress, let it complete + // Log.i(TAG, "waiting for bonding to complete"); + break; + // case Bond.None: + // // gatt.DiscoverServices(); + default: + throw new ArgumentOutOfRangeException(); + } + + + lock (_adapter.ConnectedDeviceRegistryLock) { _adapter.ConnectedDeviceRegistry[gatt.Device.Address] = _device; @@ -177,9 +212,23 @@ private void CloseGattInstances(BluetoothGatt gatt) public override void OnServicesDiscovered(BluetoothGatt gatt, GattStatus status) { base.OnServicesDiscovered(gatt, status); - + //gattstatus needs to be updated with even more states/codes +// status == GattStatus.Failure +// { +// disconect +// } Trace.Message("OnServicesDiscovered: {0}", status.ToString()); - + // status switch + // { + // The device disconnected itself on purpose. For example, because all data has been transferred and there is nothing else to to. You will receive status 19 (GATT_CONN_TERMINATE_PEER_USER). + // The connection timed out and the device disconnected itself. In this case you’ll get a status 8 (GATT_CONN_TIMEOUT) + // There was an low-level error in the communication which led to the loss of the connection. Typically you would receive a status 133 (GATT_ERROR) or a more specific error code if you are lucky! + // The stack never managed to connect in the first place. In this case you will also receive a status 133 (GATT_ERROR) + // The connection was lost during service discovery or bonding. In this case you will want to investigate why this happened and perhaps retry the connection. + // The first two cases are totally normal and there is nothing else to do than call close() and perhaps do some internal cleanup like disposing of the BluetoothGatt object. + // In the other cases, you may want to do something like informing other parts of your app or showing something in the UI. If there was a communication error you might be doing something wrong yourself. Alternatively, the device might be doing something wrong. Either way, something to deal with! It is a bit up to you to what extend you want to deal with all possible cases. + // https://github.com/weliem/blessed-android/blob/master/blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.java#L234 + // }; ServicesDiscovered?.Invoke(this, new ServicesDiscoveredCallbackEventArgs()); } @@ -257,6 +306,7 @@ private Exception GetExceptionFromGattStatus(GattStatus status) switch (status) { case GattStatus.Failure: + //close and try again (hope this is the 133 error code) case GattStatus.InsufficientAuthentication: case GattStatus.InsufficientEncryption: case GattStatus.InvalidAttributeLength: diff --git a/Source/Plugin.BLE.Android/Plugin.BLE.Android.csproj b/Source/Plugin.BLE.Android/Plugin.BLE.Android.csproj index 946752b2..9619a0e9 100644 --- a/Source/Plugin.BLE.Android/Plugin.BLE.Android.csproj +++ b/Source/Plugin.BLE.Android/Plugin.BLE.Android.csproj @@ -14,7 +14,7 @@ 512 Resources\Resource.Designer.cs Off - v8.1 + v9.0 @@ -26,6 +26,7 @@ DEBUG;TRACE prompt 4 + armeabi-v7a;x86;arm64-v8a;x86_64 pdbonly @@ -35,6 +36,7 @@ prompt 4 bin\Release\Plugin.BLE.XML + armeabi-v7a;x86;arm64-v8a;x86_64 diff --git a/Source/Plugin.BLE.Android/Properties/AssemblyInfo.cs b/Source/Plugin.BLE.Android/Properties/AssemblyInfo.cs index 1cd33552..47dd022b 100644 --- a/Source/Plugin.BLE.Android/Properties/AssemblyInfo.cs +++ b/Source/Plugin.BLE.Android/Properties/AssemblyInfo.cs @@ -1,7 +1,6 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// Copyright (C) 2020 Bang & Olufsen A/S - All Rights Reserved +using System.Reflection; using System.Runtime.InteropServices; -using Android.App; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information diff --git a/Source/Plugin.BLE.Android/Service.cs b/Source/Plugin.BLE.Android/Service.cs index 202217ef..cbdbd54b 100644 --- a/Source/Plugin.BLE.Android/Service.cs +++ b/Source/Plugin.BLE.Android/Service.cs @@ -25,6 +25,7 @@ public Service(BluetoothGattService nativeService, BluetoothGatt gatt, IGattCall _gattCallback = gattCallback; } +//max 15 characteristics per peripheral protected override Task> GetCharacteristicsNativeAsync(CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult>(