@Override public void onReceive(final Context context, final Intent intent) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Skip other devices if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().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); Logger.d( mLogSession, "[Broadcast] Action received: android.bluetooth.device.action.PAIRING_REQUEST" /*BluetoothDevice.ACTION_PAIRING_REQUEST*/ + ", pairing variant: " + pairingVariantToString(variant) + " (" + variant + ")"); // The API below is available for Android 4.4 or newer. // An app may set the PIN here or set pairing confirmation (depending on the variant) // using: // device.setPin(new byte[] { '1', '2', '3', '4', '5', '6' }); // device.setPairingConfirmation(true); }
/** * Connects to the Bluetooth Smart device * * @param device a device to connect to */ public void connect(final BluetoothDevice device) { if (mConnected) return; if (mBluetoothGatt != null) { Logger.d(mLogSession, "gatt.close()"); mBluetoothGatt.close(); mBluetoothGatt = null; } final boolean autoConnect = shouldAutoConnect(); mUserDisconnected = !autoConnect; // We will receive Linkloss events only when the device is connected with // autoConnect=true Logger.v(mLogSession, "Connecting..."); Logger.d(mLogSession, "gatt = device.connectGatt(autoConnect = " + autoConnect + ")"); mBluetoothGatt = device.connectGatt(mContext, autoConnect, getGattCallback()); }
@Override public void onReceive(final Context context, final Intent intent) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); // Skip other devices if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) return; Logger.d( mLogSession, "[Broadcast] Action received: " + BluetoothDevice.ACTION_BOND_STATE_CHANGED + ", bond state changed to: " + bondStateToString(bondState) + " (" + bondState + ")"); DebugLogger.i( TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState); switch (bondState) { case BluetoothDevice.BOND_BONDING: mCallbacks.onBondingRequired(); break; case BluetoothDevice.BOND_BONDED: Logger.i(mLogSession, "Device bonded"); mCallbacks.onBonded(); // Start initializing again. // In fact, bonding forces additional, internal service discovery (at least on Nexus // devices), so this method may safely be used to start this process again. Logger.v(mLogSession, "Discovering Services..."); Logger.d(mLogSession, "gatt.discoverServices()"); mBluetoothGatt.discoverServices(); break; } }
/** * This method tries to enable notifications on the Battery Level characteristic. * * @param enable <code>true</code> to enable battery notifications, false to disable * @return true if request has been sent */ public boolean setBatteryNotifications(final boolean enable) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null) { return false; } final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE); if (batteryService == null) return false; final BluetoothGattCharacteristic batteryLevelCharacteristic = batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); if (batteryLevelCharacteristic == null) return false; // Check characteristic property final int properties = batteryLevelCharacteristic.getProperties(); if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) return false; gatt.setCharacteristicNotification(batteryLevelCharacteristic, enable); final BluetoothGattDescriptor descriptor = batteryLevelCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); if (descriptor != null) { if (enable) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); Logger.a(mLogSession, "Enabling battery level notifications..."); Logger.v(mLogSession, "Enabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC); Logger.d( mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x01-00)"); } else { descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); Logger.a(mLogSession, "Disabling battery level notifications..."); Logger.v(mLogSession, "Disabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC); Logger.d( mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x00-00)"); } return gatt.writeDescriptor(descriptor); } return false; }
/** * Sends the read request to the given characteristic. * * @param characteristic the characteristic to read * @return true if request has been sent */ protected final boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null) return false; // Check characteristic property final int properties = characteristic.getProperties(); if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false; Logger.v(mLogSession, "Reading characteristic " + characteristic.getUuid()); Logger.d(mLogSession, "gatt.readCharacteristic(" + characteristic.getUuid() + ")"); return gatt.readCharacteristic(characteristic); }
/** * Disconnects from the device. Does nothing if not connected. * * @return true if device is to be disconnected. False if it was already disconnected. */ public boolean disconnect() { mUserDisconnected = true; if (mConnected && mBluetoothGatt != null) { Logger.v(mLogSession, "Disconnecting..."); mCallbacks.onDeviceDisconnecting(); Logger.d(mLogSession, "gatt.disconnect()"); mBluetoothGatt.disconnect(); return true; } return false; }
/** * Writes the characteristic value to the given characteristic. * * @param characteristic the characteristic to write to * @return true if request has been sent */ protected final boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null) return false; // Check characteristic property final int properties = characteristic.getProperties(); if ((properties & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) return false; Logger.v(mLogSession, "Writing characteristic " + characteristic.getUuid()); Logger.d(mLogSession, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")"); return gatt.writeCharacteristic(characteristic); }
/** * Enables indications on given characteristic * * @return true is the request has been sent, false if one of the arguments was <code>null</code> * or the characteristic does not have the CCCD. */ protected final boolean enableIndications(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null) return false; // Check characteristic property final int properties = characteristic.getProperties(); if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0) return false; gatt.setCharacteristicNotification(characteristic, true); final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); if (descriptor != null) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); Logger.v(mLogSession, "Enabling indications for " + characteristic.getUuid()); Logger.d( mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x02-00)"); return gatt.writeDescriptor(descriptor); } return false; }
@Override public final void onConnectionStateChange( final BluetoothGatt gatt, final int status, final int newState) { Logger.d( mLogSession, "[Callback] Connection state changed with status: " + status + " and new state: " + newState + " (" + stateToString(newState) + ")"); if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { // Notify the parent activity/service Logger.i(mLogSession, "Connected to " + gatt.getDevice().getAddress()); mConnected = true; mCallbacks.onDeviceConnected(); /* * The onConnectionStateChange event is triggered just after the Android connects to a device. * In case of bonded devices, the encryption is reestablished AFTER this callback is called. * Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU), * the indication is received few milliseconds later, depending on the connection interval. * When received, Android will start performing a service discovery operation itself, internally. * * If the mBluetoothGatt.discoverServices() method would be invoked here, if would returned cached services, * as the SC indication wouldn't be received yet. * Therefore we have to postpone the service discovery operation until we are (almost, as there is no such callback) sure, that it had to be handled. * Our tests has shown that 600 ms is enough. It is important to call it AFTER receiving the SC indication, but not necessarily * after Android finishes the internal service discovery. * * NOTE: This applies only for bonded devices with Service Changed characteristic, but to be sure we will postpone * service discovery for all devices. */ mHandler.postDelayed( new Runnable() { @Override public void run() { // Some proximity tags (e.g. nRF PROXIMITY) initialize bonding automatically when // connected. if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDING) { Logger.v(mLogSession, "Discovering Services..."); Logger.d(mLogSession, "gatt.discoverServices()"); gatt.discoverServices(); } } }, 600); } else { if (newState == BluetoothProfile.STATE_DISCONNECTED) { if (status != BluetoothGatt.GATT_SUCCESS) Logger.w( mLogSession, "Error: (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status)); onDeviceDisconnected(); mConnected = false; if (mUserDisconnected) { Logger.i(mLogSession, "Disconnected"); mCallbacks.onDeviceDisconnected(); close(); } else { Logger.w(mLogSession, "Connection lost"); mCallbacks.onLinklossOccur(); // We are not closing the connection here as the device should try to reconnect // automatically. // This may be only called when the shouldAutoConnect() method returned true. } return; } // TODO Should the disconnect method be called or the connection is still valid? Does this // ever happen? Logger.e( mLogSession, "Error (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status)); mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); } }