/** * Called when the service has been stopped. */ protected void onServiceStopped() { // Unregister broadcast receivers unregisterReceiver(mBluetoothStateBroadcastReceiver); // The managers map may not be empty if the service was killed by the system for (final BleManager<BleManagerCallbacks> manager : mBleManagers.values()) { // Service is being destroyed, no need to disconnect manually. manager.close(); manager.log(Log.INFO, "Service destroyed"); } mBleManagers.clear(); mManagedDevices.clear(); mBleManagers = null; mManagedDevices = null; }
/** * This method is called when Bluetooth Adapter has been enabled. It is also called * after the service was created if Bluetooth Adapter was enabled at that moment. * This method could initialize all Bluetooth related features, for example open the GATT server. * Make sure you call <code>super.onBluetoothEnabled()</code> at this methods reconnects to * devices that were connected before the Bluetooth was turned off. */ protected void onBluetoothEnabled() { for (final BluetoothDevice device : mManagedDevices) { final BleManager<BleManagerCallbacks> manager = mBleManagers.get(device); if (!manager.isConnected()) manager.connect(device).enqueue(); } }
/** * Disconnects the given device and removes the associated BleManager object. * If the list of BleManagers is empty while the last activity unbinds from the service, * the service will stop itself. * @param device target device to disconnect and forget */ public void disconnect(final BluetoothDevice device) { final BleManager<BleManagerCallbacks> manager = mBleManagers.get(device); if (manager != null && manager.isConnected()) { manager.disconnect().enqueue(); } mManagedDevices.remove(device); }
/** * Enqueues a new request. * * @param request the new request to be added to the end of the queue. * @deprecated The access modifier of this method will be changed to package only. */ @Deprecated protected final void enqueue(@NonNull final Request request) { BleManagerGattCallback callback = mGattCallback; if (callback == null) { callback = mGattCallback = getGattCallback(); } // Add the new task to the end of the queue. final BleManagerGattCallback finalCallback = callback; finalCallback.enqueue(request); runOnUiThread(() -> finalCallback.nextRequest(false)); }
@Override public void log(final BluetoothDevice device, final int level, final String message) { final BleManager<BleManagerCallbacks> manager = mBleManagers.get(device); if (manager != null) manager.log(level, message); }
@MainThread private boolean internalEnableNotifications(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null || !mConnected) return false; final BluetoothGattDescriptor descriptor = getCccd(characteristic, BluetoothGattCharacteristic.PROPERTY_NOTIFY); if (descriptor != null) { log(Log.DEBUG, "gatt.setCharacteristicNotification(" + characteristic.getUuid() + ", true)"); gatt.setCharacteristicNotification(characteristic, true); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); log(Log.VERBOSE, "Enabling notifications for " + characteristic.getUuid()); log(Log.DEBUG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x01-00)"); return internalWriteDescriptorWorkaround(descriptor); } return false; }
@Override public void onReceive(final Context context, final Intent intent) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Skip other devices. if (mBluetoothDevice == null || device == null || !device.getAddress().equals(mBluetoothDevice.getAddress())) return; // String values are used as the constants are not available for Android 4.3. final int variant = intent.getIntExtra("android.bluetooth.device.extra.PAIRING_VARIANT"/*BluetoothDevice.EXTRA_PAIRING_VARIANT*/, 0); log(Log.DEBUG, "[Broadcast] Action received: android.bluetooth.device.action.PAIRING_REQUEST"/*BluetoothDevice.ACTION_PAIRING_REQUEST*/ + ", pairing variant: " + pairingVariantToString(variant) + " (" + variant + ")"); onPairingRequestReceived(device, variant); } };
@RequiresApi(api = Build.VERSION_CODES.O) @MainThread private boolean internalSetPreferredPhy(@PhyMask final int txPhy, @PhyMask final int rxPhy, @PhyOption final int phyOptions) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || !mConnected) return false; log(Log.VERBOSE, "Requesting preferred PHYs..."); log(Log.DEBUG, "gatt.setPreferredPhy(" + phyMaskToString(txPhy) + ", " + phyMaskToString(rxPhy) + ", coding option = " + phyCodedOptionToString(phyOptions) + ")"); gatt.setPreferredPhy(txPhy, rxPhy, phyOptions); return true; }
if (shouldClearCacheWhenDisconnected()) { if (internalRefreshDeviceCache()) { log(Log.INFO, "Cache refreshed"); } else { log(Log.WARN, "Refreshing failed"); log(Log.DEBUG, "gatt.close()"); mBluetoothGatt.close(); mBluetoothGatt = null;
@MainThread private boolean internalWriteDescriptor(final BluetoothGattDescriptor descriptor) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || descriptor == null || !mConnected) return false; log(Log.VERBOSE, "Writing descriptor " + descriptor.getUuid()); log(Log.DEBUG, "gatt.writeDescriptor(" + descriptor.getUuid() + ")"); return internalWriteDescriptorWorkaround(descriptor); }
@MainThread @Override void onRequestTimeout(@NonNull final TimeoutableRequest request) { mRequest = null; mValueChangedRequest = null; if (request.type == Request.Type.CONNECT) { mConnectRequest = null; internalDisconnect(); // The method above will call mGattCallback.nextRequest(true) so we have to return here. return; } if (request.type == Request.Type.DISCONNECT) { close(); return; } final BleManagerGattCallback callback = mGattCallback; if (callback != null) { callback.nextRequest(true); } }
@MainThread private boolean internalWriteCharacteristic(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null || !mConnected) return false; // Check characteristic property. final int properties = characteristic.getProperties(); if ((properties & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) return false; log(Log.VERBOSE, "Writing characteristic " + characteristic.getUuid() + " (" + writeTypeToString(characteristic.getWriteType()) + ")"); log(Log.DEBUG, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")"); return gatt.writeCharacteristic(characteristic); }
/** * When the device is bonded and has the Generic Attribute service and the Service Changed * characteristic this method enables indications on this characteristic. * In case one of the requirements is not fulfilled this method returns <code>false</code>. * * @return <code>true</code> when the request has been sent, <code>false</code> when the device * is not bonded, does not have the Generic Attribute service, the GA service does not have * the Service Changed characteristic or this characteristic does not have the CCCD. */ private boolean ensureServiceChangedEnabled() { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || !mConnected) return false; // The Service Changed indications have sense only on bonded devices. final BluetoothDevice device = gatt.getDevice(); if (device.getBondState() != BluetoothDevice.BOND_BONDED) return false; final BluetoothGattService gaService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE); if (gaService == null) return false; final BluetoothGattCharacteristic scCharacteristic = gaService.getCharacteristic(SERVICE_CHANGED_CHARACTERISTIC); if (scCharacteristic == null) return false; log(Log.INFO, "Service Changed characteristic found on a bonded device"); return internalEnableIndications(scCharacteristic); }
return; log(Log.DEBUG, "[Broadcast] Action received: " + BluetoothDevice.ACTION_BOND_STATE_CHANGED + ", bond state changed to: " + bondStateToString(bondState) + " (" + bondState + ")"); if (previousBondState == BluetoothDevice.BOND_BONDING) { mCallbacks.onBondingFailed(device); log(Log.WARN, "Bonding failed"); if (mRequest != null) { // CREATE_BOND request mRequest.notifyFail(device, FailCallback.REASON_REQUEST_FAILED); log(Log.INFO, "Bond information removed"); mRequest.notifySuccess(device); mRequest = null; return; case BluetoothDevice.BOND_BONDED: log(Log.INFO, "Device bonded"); mCallbacks.onBonded(device); if (mRequest != null && mRequest.type == Request.Type.CREATE_BOND) { mServiceDiscoveryRequested = true; mHandler.post(() -> { log(Log.VERBOSE, "Discovering services..."); log(Log.DEBUG, "gatt.discoverServices()"); mBluetoothGatt.discoverServices(); });
log(Log.DEBUG, "gatt.close()"); mBluetoothGatt.close(); mBluetoothGatt = null; try { log(Log.DEBUG, "wait(200)"); Thread.sleep(200); // Is 200 ms enough? } catch (final InterruptedException e) { log(Log.VERBOSE, "Connecting..."); mCallbacks.onDeviceConnecting(device); log(Log.DEBUG, "gatt.connect()"); mBluetoothGatt.connect(); return true; log(Log.VERBOSE, connectRequest.isFirstAttempt() ? "Connecting..." : "Retrying..."); mCallbacks.onDeviceConnecting(device); mConnectionTime = SystemClock.elapsedRealtime(); log(Log.DEBUG, "gatt = device.connectGatt(autoConnect = false, TRANSPORT_LE, " + phyMaskToString(preferredPhy) + ")"); log(Log.DEBUG, "gatt = device.connectGatt(autoConnect = false, TRANSPORT_LE)"); mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback, BluetoothDevice.TRANSPORT_LE); } else { log(Log.DEBUG, "gatt = device.connectGatt(autoConnect = false)"); mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
/** * Enqueues the request for asynchronous execution. */ public void enqueue() { manager.enqueue(this); }
/** * Returns the connection state of given device. * @param device the target device * @return the connection state, as in {@link BleManager#getConnectionState()}. */ public final int getConnectionState(final BluetoothDevice device) { final BleManager<BleManagerCallbacks> manager = mBleManagers.get(device); return manager != null ? manager.getConnectionState() : BluetoothGatt.STATE_DISCONNECTED; }
/** * Returns the last received battery level value. * @param device the device of which battery level should be returned * @return battery value or -1 if no value was received or Battery Level characteristic was not found * @deprecated Keep battery value in your manager instead. */ @Deprecated public int getBatteryValue(final BluetoothDevice device) { final BleManager<BleManagerCallbacks> manager = mBleManagers.get(device); return manager.getBatteryValue(); }
@Override public void log(final int level, @StringRes final int messageRes, final Object... params) { for (final BleManager<BleManagerCallbacks> manager : mBleManagers.values()) manager.log(level, messageRes, params); } }
@MainThread private boolean internalEnableIndications(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null || !mConnected) return false; final BluetoothGattDescriptor descriptor = getCccd(characteristic, BluetoothGattCharacteristic.PROPERTY_INDICATE); if (descriptor != null) { log(Log.DEBUG, "gatt.setCharacteristicNotification(" + characteristic.getUuid() + ", true)"); gatt.setCharacteristicNotification(characteristic, true); descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); log(Log.VERBOSE, "Enabling indications for " + characteristic.getUuid()); log(Log.DEBUG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x02-00)"); return internalWriteDescriptorWorkaround(descriptor); } return false; }