public async Task ConnectAsync(string macAddress, string serviceUuid, bool noRetry = false)
        {
            if (IsConnecting)
            {
                Log.Debug($"WindowsRT.BluetoothService: Already connecting. Skipping request.");
                return;
            }
            if (IsStreamConnected)
            {
                Log.Debug($"WindowsRT.BluetoothService: Already connected. Skipping request.");
                return;
            }

            Connecting?.Invoke(this, EventArgs.Empty);

            try
            {
                var matches = _deviceCache.Where(x =>
                                                 string.Equals(x.Address, macAddress, StringComparison.CurrentCultureIgnoreCase)).ToList();
                if (matches.Count <= 0)
                {
                    Log.Error(
                        $"WindowsRT.BluetoothService: Registered device not available. Expected MAC: {macAddress}");
                    BluetoothErrorAsync?.Invoke(this, new BluetoothException(
                                                    BluetoothException.ErrorCodes.ConnectFailed,
                                                    "Device unavailable. Not device with registered MAC address not found nearby. If you are certain that your earbuds are connected to this computer, please unregister them and try again."));
                }
                else
                {
                    Log.Debug(
                        $"WindowsRT.BluetoothService: Selected '{matches[0].Name}' ({matches[0].Address}) from cache as target");
                }

                // Perform device access checks before trying to get the device.
                // First, we check if consent has been explicitly denied by the user.
                var accessStatus = DeviceAccessInformation.CreateFromId(matches[0].Id).CurrentStatus;
                if (accessStatus == DeviceAccessStatus.DeniedByUser)
                {
                    Log.Error($"WindowsRT.BluetoothService: Access to device explicitly denied by user");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "This app does not have access to connect to the remote device (please grant access in Settings > Privacy > Other Devices"));
                    return;
                }

                if (accessStatus == DeviceAccessStatus.DeniedBySystem)
                {
                    Log.Error($"WindowsRT.BluetoothService: Access to device denied by system");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Access denied by system. This app does not have access to connect to the remote device"));
                    return;
                }

                // If not, try to get the Bluetooth device
                try
                {
                    _bluetoothDevice = await Windows.Devices.Bluetooth.BluetoothDevice.FromIdAsync(matches[0].Id);
                }
                catch (Exception ex)
                {
                    Log.Error(
                        $"WindowsRT.BluetoothService: Error while getting Bluetooth device from cached id: {ex.Message}");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       ex.Message));
                    return;
                }

                // If we were unable to get a valid Bluetooth device object,
                // it's most likely because the user has specified that all unpaired devices
                // should not be interacted with.
                if (_bluetoothDevice == null)
                {
                    Log.Error($"WindowsRT.BluetoothService: BluetoothDevice.FromIdAsync returned NULL");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Unable to retrieve device object. Try to re-pair your Bluetooth device."));
                    return;
                }

                // This should return a list of uncached Bluetooth services (so if the server was not active when paired, it will still be detected by this call
                var rfcommServices = await _bluetoothDevice.GetRfcommServicesForIdAsync(
                    RfcommServiceId.FromUuid(new Guid(serviceUuid)), BluetoothCacheMode.Uncached);

                if (rfcommServices.Services.Count > 0)
                {
                    _service = rfcommServices.Services[0];
                }
                else
                {
                    Log.Error($"WindowsRT.BluetoothService: SPP service not discovered");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Unable to discover SDP record for the RFCOMM protocol. Either your earbuds are out of range or ran out of battery."));
                    return;
                }

                lock (this)
                {
                    _socket = new StreamSocket();
                }

                try
                {
                    await _socket.ConnectAsync(_service.ConnectionHostName, _service.ConnectionServiceName);

                    Connected?.Invoke(this, EventArgs.Empty);
                    Log.Debug($"WindowsRT.BluetoothService: Connected");

                    _writer = new DataWriter(_socket.OutputStream);

                    Log.Debug("WindowsRT.BluetoothService: Launching BluetoothServiceLoop...");
                    RfcommConnected?.Invoke(this, EventArgs.Empty);

                    IsStreamConnected = true;

                    _loopCancellation = new CancellationTokenSource();
                    _loop             = Task.Run(BluetoothServiceLoop);
                }
                catch (Exception ex) when((uint)ex.HResult == 0x80070490)   // ERROR_ELEMENT_NOT_FOUND
                {
                    Log.Error(
                        "WindowsRT.BluetoothService: Error while connecting (HRESULT: ERROR_ELEMENT_NOT_FOUND): " +
                        ex.Message);
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "SPP server on remote device unavailable. Please reboot your earbuds by placing both into the case and closing it. (ERROR_ELEMENT_NOT_FOUND)"));
                }
                catch (Exception ex) when((uint)ex.HResult == 0x80072740)   // WSAEADDRINUSE
                {
                    Log.Error("WindowsRT.BluetoothService: Address already in use");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Target address already in use. Only one app can talk to the Galaxy Buds at a time. " +
                                                                       "Please make sure to close duplicate instances of this app and close all applications that are interacting with the proprietary RFCOMM protocol, such as Samsung's official firmware updater"));
                }
            }
            catch (Exception ex)
            {
                Log.Error("WindowsRT.BluetoothService: Unknown error while connecting: " + ex);
                BluetoothErrorAsync?.Invoke(this,
                                            new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                   ex.Message));
            }
        }
Ejemplo n.º 2
0
        private void BluetoothServiceLoop()
        {
            Stream?peerStream = null;

            while (true)
            {
                try
                {
                    _cancelSource.Token.ThrowIfCancellationRequested();
                    Task.Delay(50).Wait(_cancelSource.Token);
                }
                catch (OperationCanceledException)
                {
                    peerStream?.Close();
                    _client?.Close();
                    throw;
                }

                if (_client == null || !_client.Connected)
                {
                    continue;
                }

                if (peerStream == null)
                {
                    lock (_btlock)
                    {
                        if (!_client.Connected)
                        {
                            continue;
                        }

                        peerStream = _client.GetStream();
                    }

                    RfcommConnected?.Invoke(this, EventArgs.Empty);
                    continue;
                }


                var available = _client.Available;
                if (available > 0 && peerStream.CanRead)
                {
                    byte[] buffer = new byte[available];
                    try
                    {
                        peerStream.Read(buffer, 0, available);
                    }
                    catch (SocketException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: SocketException thrown while reading from socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.ReceiveFailed, ex.Message));
                        return;
                    }
                    catch (IOException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: IOException thrown while writing to socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.ReceiveFailed, ex.Message));
                    }

                    if (buffer.Length > 0)
                    {
                        NewDataAvailable?.Invoke(this, buffer);
                    }
                }

                lock (TransmitterQueue)
                {
                    if (TransmitterQueue.Count <= 0)
                    {
                        continue;
                    }
                    if (!TransmitterQueue.TryDequeue(out var raw))
                    {
                        continue;
                    }
                    try
                    {
                        peerStream.Write(raw, 0, raw.Length);
                    }
                    catch (SocketException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: SocketException thrown while writing to socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.SendFailed, ex.Message));
                    }
                    catch (IOException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: IOException thrown while writing to socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.SendFailed, ex.Message));
                    }
                }
            }
        }