diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+ * The BleManager is responsible for managing the low level communication with a Bluetooth LE device. + * Please see profiles implementation in Android nRF Blinky or Android nRF Toolbox app for an + * example of use. + *
+ * This base manager has been tested against number of devices and samples from Nordic SDK. + *
+ * The manager handles connection events and initializes the device after establishing the connection. + *
+ * The manager also is responsible for parsing the Battery Level values and calling
+ * {@link BleManagerCallbacks#onBatteryValueReceived(BluetoothDevice, int)} method.
+ *
+ * To get logs, override the {@link #log(int, String)} method. + *
+ * The BleManager should be overridden in your app and all the 'high level' callbacks should
+ * be called from there. Keeping this file as is (and {@link BleManagerCallbacks} as well)
+ * will allow to quickly update it when an update is posted here.
+ *
+ * @param
+ * This request has a separate reference, as it is notified when the device becomes ready,
+ * after the initialization requests are done.
+ */
+ private ConnectRequest mConnectRequest;
+ /**
+ * Currently performed request or null in idle state.
+ */
+ private Request mRequest;
+ /**
+ * Currently performer request set, or null if none.
+ */
+ private RequestQueue mRequestQueue;
+ /**
+ * A map of {@link ValueChangedCallback}s for handling notifications and indications.
+ */
+ private final HashMap
+ * After constructing the manager, the callbacks object must be set with
+ * {@link #setGattCallbacks(BleManagerCallbacks)}.
+ *
+ * To connect a device, call {@link #connect(BluetoothDevice)}.
+ *
+ * @param context the context.
+ */
+ public BleManager(@NonNull final Context context) {
+ mContext = context.getApplicationContext();
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * Sets the manager callback listener.
+ *
+ * @param callbacks the callback listener.
+ */
+ public void setGattCallbacks(@NonNull final E callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * This method must return the gatt callback used by the manager.
+ * This method must not create a new gatt callback each time it is being invoked, but rather
+ * return a single object.
+ *
+ * @return The gatt callback object.
+ */
+ @NonNull
+ protected abstract BleManagerGattCallback getGattCallback();
+
+ /**
+ * Returns the context that the manager was created with.
+ *
+ * @return The context.
+ */
+ @NonNull
+ protected final Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns the Bluetooth device object used in {@link #connect(BluetoothDevice)}.
+ *
+ * @return The Bluetooth device or null, if {@link #connect(BluetoothDevice)} wasn't called.
+ */
+ @Nullable
+ // This method is not final, as some Managers may be created with BluetoothDevice in a
+ // constructor. Those can return the device object even without calling connect(device).
+ public BluetoothDevice getBluetoothDevice() {
+ return mBluetoothDevice;
+ }
+
+ /**
+ * This method returns true if the device is connected. Services could have not been
+ * discovered yet.
+ */
+ public final boolean isConnected() {
+ return mConnected;
+ }
+
+ /**
+ * Returns whether the target device is bonded. The device does not have to be connected,
+ * but must have been set prior to call this method.
+ *
+ * @return True, if the Android has bonds information of the device. This does not mean that
+ * the target device also has such information, or that the link is in fact encrypted.
+ */
+ protected final boolean isBonded() {
+ return mBluetoothDevice != null
+ && mBluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED;
+ }
+
+ /**
+ * Returns true if the device is connected and the initialization has finished,
+ * that is when {@link BleManagerGattCallback#onDeviceReady()} was called.
+ */
+ public final boolean isReady() {
+ return mReady;
+ }
+
+ /**
+ * Method returns the connection state:
+ * {@link BluetoothProfile#STATE_CONNECTING STATE_CONNECTING},
+ * {@link BluetoothProfile#STATE_CONNECTED STATE_CONNECTED},
+ * {@link BluetoothProfile#STATE_DISCONNECTING STATE_DISCONNECTING},
+ * {@link BluetoothProfile#STATE_DISCONNECTED STATE_DISCONNECTED}
+ *
+ * @return The connection state.
+ */
+ @ConnectionState
+ public final int getConnectionState() {
+ return mConnectionState;
+ }
+
+ /**
+ * Returns the last received value of Battery Level characteristic, or -1 if such
+ * does not exist, hasn't been read or notification wasn't received yet.
+ *
+ * The value returned will be invalid if overridden {@link #readBatteryLevel()} and
+ * {@link #enableBatteryLevelNotifications()} were used.
+ *
+ * @return The last battery level value in percent.
+ * @deprecated Keep the battery level in your manager instead.
+ */
+ @IntRange(from = -1, to = 100)
+ @Deprecated
+ public final int getBatteryValue() {
+ return mBatteryValue;
+ }
+
+ @Override
+ public void log(final int priority, @NonNull final String message) {
+ // Override to log events. Simple log can use Logcat:
+ //
+ // Log.println(priority, TAG, message);
+ //
+ // You may also use Timber:
+ //
+ // Timber.log(priority, message);
+ //
+ // or nRF Logger:
+ //
+ // Logger.log(logSession, LogContract.Log.Level.fromPriority(priority), message);
+ //
+ // Starting from nRF Logger 2.1.3, you may use log-timber and plant nRFLoggerTree.
+ // https://github.com/NordicSemiconductor/nRF-Logger-API
+ }
+
+ @Override
+ public void log(final int priority, @StringRes final int messageRes,
+ @Nullable final Object... params) {
+ final String message = mContext.getString(messageRes, params);
+ log(priority, message);
+ }
+
+ /**
+ * Returns whether to connect to the remote device just once (false) or to add the address to
+ * white list of devices that will be automatically connect as soon as they become available
+ * (true). In the latter case, if Bluetooth adapter is enabled, Android scans periodically
+ * for devices from the white list and if a advertising packet is received from such, it tries
+ * to connect to it. When the connection is lost, the system will keep trying to reconnect to
+ * it in. If true is returned, and the connection to the device is lost the
+ * {@link BleManagerCallbacks#onLinkLossOccurred(BluetoothDevice)} callback is called instead of
+ * {@link BleManagerCallbacks#onDeviceDisconnected(BluetoothDevice)}.
+ *
+ * This feature works much better on newer Android phone models and many not work on older
+ * phones.
+ *
+ * This method should only be used with bonded devices, as otherwise the device may change
+ * it's address. It will however work also with non-bonded devices with private static address.
+ * A connection attempt to a device with private resolvable address will fail.
+ *
+ * The first connection to a device will always be created with autoConnect flag to false
+ * (see {@link BluetoothDevice#connectGatt(Context, boolean, BluetoothGattCallback)}). This is
+ * to make it quick as the user most probably waits for a quick response.
+ * However, if this method returned true during first connection and the link was lost,
+ * the manager will try to reconnect to it using {@link BluetoothGatt#connect()} which forces
+ * autoConnect to true.
+ *
+ * @return The AutoConnect flag value.
+ * @deprecated Use {@link ConnectRequest#useAutoConnect(boolean)} instead.
+ */
+ @Deprecated
+ protected boolean shouldAutoConnect() {
+ return false;
+ }
+
+ /**
+ * Returns whether the device cache should be cleared after the device disconnected,
+ * before calling {@link BluetoothGatt#close()}. By default it returns false.
+ *
+ * If the returned value is true, the next time the Android device will connect to
+ * this peripheral the services will be discovered again. If false, the services
+ * will be obtained from the cache.
+ *
+ * Note, that the {@link BluetoothGatt#refresh()} method is not in the public API and it
+ * is not recommended to use this. However, as Android is caching services of all devices,
+ * even if they are not bonded and have Service Changed characteristic, it may necessary to
+ * clear the cache manually.
+ *
+ * On older Android versions clearing device cache helped with connection stability.
+ * It was common to get error 133 on the second and following connections when services were
+ * obtained from the cache. However, full service discovery takes time and consumes peripheral's
+ * battery.
+ *
+ * @return True, if the device cache should be cleared after the device disconnects or false,
+ * (default) if the cached value be used.
+ */
+ @SuppressWarnings("JavadocReference")
+ protected boolean shouldClearCacheWhenDisconnected() {
+ return false;
+ }
+
+ /**
+ * 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 hundred milliseconds later,
+ * depending on the connection interval.
+ * When received, Android will start performing a service discovery operation, internally,
+ * and will NOT notify the app that services has changed.
+ *
+ * If the gatt.discoverServices() method would be invoked here with no delay, if would return
+ * 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 has been handled. It should be greater than the time from
+ * LLCP Feature Exchange to ATT Write for Service Change indication.
+ *
+ * If your device does not use Service Change indication (for example does not have DFU)
+ * the delay may be 0.
+ *
+ * Please calculate the proper delay that will work in your solution.
+ *
+ * For devices that are not bonded, but support paiing, a small delay is required on some
+ * older Android versions (Nexus 4 with Android 5.1.1) when the device will send pairing
+ * request just after connection. If so, we want to wait with the service discovery until
+ * bonding is complete.
+ *
+ * The default implementation returns 1600 ms for bonded and 300 ms when the device is not
+ * bonded to be compatible with older versions of the library.
+ */
+ @IntRange(from = 0)
+ protected int getServiceDiscoveryDelay(final boolean bonded) {
+ return bonded ? 1600 : 300;
+ }
+
+ /**
+ * Creates a Connect request that will try to connect to the given Bluetooth LE device.
+ * Call {@link ConnectRequest#enqueue()} or {@link ConnectRequest#await()} in order to execute
+ * the request.
+ *
+ * This method returns a {@link ConnectRequest} which can be used to set completion
+ * and failure callbacks. The completion callback (done) will be called after the initialization
+ * is complete, after {@link BleManagerCallbacks#onDeviceReady(BluetoothDevice)} has been
+ * called.
+ *
+ * Calling {@link ConnectRequest#await()} will make this request
+ * synchronous (the callbacks set will be ignored, instead the synchronous method will
+ * return or throw an exception).
+ *
+ * For asynchronous call usage, {@link ConnectRequest#enqueue()} must be called on the returned
+ * request.
+ *
+ * The callbacks observer must be set with {@link #setGattCallbacks(BleManagerCallbacks)}
+ * before calling this method.
+ *
+ * @param device a device to connect to.
+ * @return The connect request.
+ */
+ @SuppressWarnings("ConstantConditions")
+ @NonNull
+ public final ConnectRequest connect(@NonNull final BluetoothDevice device) {
+ if (mCallbacks == null) {
+ throw new NullPointerException("Set callbacks using setGattCallbacks(E callbacks) before connecting");
+ }
+ if (device == null) {
+ throw new NullPointerException("Bluetooth device not specified");
+ }
+ return Request.connect(device)
+ .useAutoConnect(shouldAutoConnect())
+ .setManager(this);
+ }
+
+ /**
+ * Creates a Connect request that will try to connect to the given Bluetooth LE device using
+ * preferred PHY. Call {@link ConnectRequest#enqueue()} or {@link ConnectRequest#await()}
+ * in order to execute the request.
+ *
+ * This method returns a {@link ConnectRequest} which can be used to set completion
+ * and failure callbacks. The completion callback will be called after the initialization
+ * is complete, after {@link BleManagerCallbacks#onDeviceReady(BluetoothDevice)} has been
+ * called.
+ *
+ * Calling {@link ConnectRequest#await()} will make this request
+ * synchronous (the callbacks set will be ignored, instead the synchronous method will
+ * return or throw an exception).
+ *
+ * For asynchronous call usage, {@link ConnectRequest#enqueue()} must be called on the returned
+ * request.
+ *
+ * The callbacks observer must be set with {@link #setGattCallbacks(BleManagerCallbacks)}
+ * before calling this method.
+ *
+ * @param device a device to connect to.
+ * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK},
+ * and {@link PhyRequest#PHY_LE_CODED_MASK}. This option does not take effect
+ * if {@code autoConnect} is set to true. PHY 2M and Coded are supported
+ * on newer devices running Android Oreo or newer.
+ * @return The connect request.
+ * @deprecated Use {@link #connect(BluetoothDevice)} instead and set preferred PHY using
+ * {@link ConnectRequest#usePreferredPhy(int)}.
+ */
+ @SuppressWarnings("ConstantConditions")
+ @NonNull
+ @Deprecated
+ public final ConnectRequest connect(@NonNull final BluetoothDevice device, @PhyMask final int phy) {
+ if (mCallbacks == null) {
+ throw new NullPointerException("Set callbacks using setGattCallbacks(E callbacks) before connecting");
+ }
+ if (device == null) {
+ throw new NullPointerException("Bluetooth device not specified");
+ }
+ return Request.connect(device)
+ .usePreferredPhy(phy)
+ .useAutoConnect(shouldAutoConnect())
+ .setManager(this);
+ }
+
+ @MainThread
+ private boolean internalConnect(@NonNull final BluetoothDevice device,
+ @Nullable final ConnectRequest connectRequest) {
+ final boolean bluetoothEnabled = BluetoothAdapter.getDefaultAdapter().isEnabled();
+ if (mConnected || !bluetoothEnabled) {
+ final BluetoothDevice currentDevice = mBluetoothDevice;
+ if (bluetoothEnabled && currentDevice != null && currentDevice.equals(device)) {
+ mConnectRequest.notifySuccess(device);
+ } else {
+ // We can't return false here, as the request would be notified with
+ // mBluetoothDevice instance instead, and that may be null or a wrong device.
+ if (mConnectRequest != null) {
+ mConnectRequest.notifyFail(device,
+ bluetoothEnabled ?
+ FailCallback.REASON_REQUEST_FAILED :
+ FailCallback.REASON_BLUETOOTH_DISABLED);
+ } // else, the request was already failed by the Bluetooth state receiver
+ }
+ mConnectRequest = null;
+ if (mGattCallback != null) {
+ mGattCallback.nextRequest(true);
+ }
+ return true;
+ }
+
+ synchronized (mLock) {
+ if (mBluetoothGatt != null) {
+ // There are 2 ways of reconnecting to the same device:
+ // 1. Reusing the same BluetoothGatt object and calling connect() - this will force
+ // the autoConnect flag to true
+ // 2. Closing it and reopening a new instance of BluetoothGatt object.
+ // The gatt.close() is an asynchronous method. It requires some time before it's
+ // finished and device.connectGatt(...) can't be called immediately or service
+ // discovery may never finish on some older devices (Nexus 4, Android 5.0.1).
+ // If shouldAutoConnect() method returned false we can't call gatt.connect() and
+ // have to close gatt and open it again.
+ if (!mInitialConnection) {
+ log(Log.DEBUG, "gatt.close()");
+ try {
+ mBluetoothGatt.close();
+ } catch (final Throwable t) {
+ // ignore
+ }
+ mBluetoothGatt = null;
+ try {
+ log(Log.DEBUG, "wait(200)");
+ Thread.sleep(200); // Is 200 ms enough?
+ } catch (final InterruptedException e) {
+ // Ignore
+ }
+ } else {
+ // Instead, the gatt.connect() method will be used to reconnect to the same device.
+ // This method forces autoConnect = true even if the gatt was created with this
+ // flag set to false.
+ mInitialConnection = false;
+ mConnectionTime = 0L; // no timeout possible when autoConnect used
+ mConnectionState = BluetoothGatt.STATE_CONNECTING;
+ log(Log.VERBOSE, "Connecting...");
+ mCallbacks.onDeviceConnecting(device);
+ log(Log.DEBUG, "gatt.connect()");
+ mBluetoothGatt.connect();
+ return true;
+ }
+ } else {
+ // Register bonding broadcast receiver
+ mContext.registerReceiver(mBluetoothStateBroadcastReceiver,
+ new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+ mContext.registerReceiver(mBondingBroadcastReceiver,
+ new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
+ mContext.registerReceiver(mPairingRequestBroadcastReceiver,
+ //BluetoothDevice.ACTION_PAIRING_REQUEST
+ new IntentFilter("android.bluetooth.device.action.PAIRING_REQUEST"));
+ }
+ }
+
+ // This should not happen in normal circumstances, but may, when Bluetooth was turned off
+ // when retrying to create a connection.
+ if (connectRequest == null)
+ return false;
+ final boolean shouldAutoConnect = connectRequest.shouldAutoConnect();
+ // We will receive Link Loss events only when the device is connected with autoConnect=true.
+ mUserDisconnected = !shouldAutoConnect;
+ // The first connection will always be done with autoConnect = false to make the connection quick.
+ // If the shouldAutoConnect() method returned true, the manager will automatically try to
+ // reconnect to this device on link loss.
+ if (shouldAutoConnect) {
+ mInitialConnection = true;
+ }
+ mBluetoothDevice = device;
+ mGattCallback.setHandler(mHandler);
+ log(Log.VERBOSE, connectRequest.isFirstAttempt() ? "Connecting..." : "Retrying...");
+ mConnectionState = BluetoothGatt.STATE_CONNECTING;
+ mCallbacks.onDeviceConnecting(device);
+ mConnectionTime = SystemClock.elapsedRealtime();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // connectRequest will never be null here.
+ final int preferredPhy = connectRequest.getPreferredPhy();
+ log(Log.DEBUG, "gatt = device.connectGatt(autoConnect = false, TRANSPORT_LE, "
+ + phyMaskToString(preferredPhy) + ")");
+ // A variant of connectGatt with Handled can't be used here.
+ // Check https://github.com/NordicSemiconductor/Android-BLE-Library/issues/54
+ mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback,
+ BluetoothDevice.TRANSPORT_LE, preferredPhy/*, mHandler*/);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ 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);
+ }
+ return true;
+ }
+
+ /**
+ * Disconnects from the device or cancels the pending connection attempt.
+ * Does nothing if device was not connected.
+ *
+ * @return The disconnect request. The completion callback will be called after the device
+ * has disconnected and the connection was closed. If the device was not connected,
+ * the completion callback will be called immediately with device parameter set to null.
+ */
+ @NonNull
+ public final DisconnectRequest disconnect() {
+ return Request.disconnect().setManager(this);
+ }
+
+ @MainThread
+ private boolean internalDisconnect() {
+ mUserDisconnected = true;
+ mInitialConnection = false;
+ mReady = false;
+
+ if (mBluetoothGatt != null) {
+ mConnectionState = BluetoothGatt.STATE_DISCONNECTING;
+ log(Log.VERBOSE, mConnected ? "Disconnecting..." : "Cancelling connection...");
+ mCallbacks.onDeviceDisconnecting(mBluetoothGatt.getDevice());
+ final boolean wasConnected = mConnected;
+ log(Log.DEBUG, "gatt.disconnect()");
+ mBluetoothGatt.disconnect();
+
+ if (wasConnected)
+ return true;
+
+ // If the device wasn't connected, there will be no callback after calling
+ // gatt.disconnect(), the connection attempt will be stopped.
+ mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
+ log(Log.INFO, "Disconnected");
+ mCallbacks.onDeviceDisconnected(mBluetoothGatt.getDevice());
+ }
+ // mRequest may be of type DISCONNECT or CONNECT (timeout).
+ // For the latter, it has already been notified with REASON_TIMEOUT.
+ if (mRequest != null && mRequest.type == Request.Type.DISCONNECT) {
+ if (mBluetoothDevice != null)
+ mRequest.notifySuccess(mBluetoothDevice);
+ else
+ mRequest.notifyInvalidRequest();
+ }
+ if (mGattCallback != null) {
+ mGattCallback.nextRequest(true);
+ }
+ return true;
+ }
+
+ /**
+ * Closes and releases resources. This method will be called automatically after
+ * calling {@link #disconnect()}. When the device disconnected with link loss and
+ * {@link #shouldAutoConnect()} returned true you have call this method to close the connection.
+ */
+ public void close() {
+ try {
+ mContext.unregisterReceiver(mBluetoothStateBroadcastReceiver);
+ mContext.unregisterReceiver(mBondingBroadcastReceiver);
+ mContext.unregisterReceiver(mPairingRequestBroadcastReceiver);
+ } catch (final Exception e) {
+ // the receiver must have been not registered or unregistered before.
+ }
+ synchronized (mLock) {
+ if (mBluetoothGatt != null) {
+ if (shouldClearCacheWhenDisconnected()) {
+ if (internalRefreshDeviceCache()) {
+ log(Log.INFO, "Cache refreshed");
+ } else {
+ log(Log.WARN, "Refreshing failed");
+ }
+ }
+ log(Log.DEBUG, "gatt.close()");
+ try {
+ mBluetoothGatt.close();
+ } catch (final Throwable t) {
+ // ignore
+ }
+ mBluetoothGatt = null;
+ }
+ mConnected = false;
+ mInitialConnection = false;
+ mReliableWriteInProgress = false;
+ mNotificationCallbacks.clear();
+ mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
+ if (mGattCallback != null) {
+ // close() is called in notifyDeviceDisconnected, which may enqueue new requests.
+ // Setting this flag to false would allow to enqueue a new request before the
+ // current one ends processing. The following line should not be uncommented.
+ // mGattCallback.mOperationInProgress = false;
+ mGattCallback.cancelQueue();
+ mGattCallback.mInitQueue = null;
+ }
+ mGattCallback = null;
+ mBluetoothDevice = null;
+ }
+ }
+
+ /**
+ * Returns a request to create bond with the device. The device must be first set using
+ * {@link #connect(BluetoothDevice)} which will try to connect to the device.
+ * If you need to pair with a device before connecting to it you may do it without
+ * the use of BleManager object and connect after bond is established.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @return The request.
+ */
+ @NonNull
+ protected Request createBond() {
+ return Request.createBond().setManager(this);
+ }
+
+ @MainThread
+ private boolean internalCreateBond() {
+ final BluetoothDevice device = mBluetoothDevice;
+ if (device == null)
+ return false;
+
+ log(Log.VERBOSE, "Starting pairing...");
+
+ if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
+ log(Log.WARN, "Device already bonded");
+ mRequest.notifySuccess(device);
+ mGattCallback.nextRequest(true);
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ log(Log.DEBUG, "device.createBond()");
+ return device.createBond();
+ } else {
+ /*
+ * There is a createBond() method in BluetoothDevice class but for now it's hidden.
+ * We will call it using reflections. It has been revealed in KitKat (Api19).
+ */
+ try {
+ final Method createBond = device.getClass().getMethod("createBond");
+ log(Log.DEBUG, "device.createBond() (hidden)");
+ return (Boolean) createBond.invoke(device);
+ } catch (final Exception e) {
+ Log.w(TAG, "An exception occurred while creating bond", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Enqueues removing bond information. When the device was bonded and the bond
+ * information was successfully removed, the device will disconnect.
+ * Note, that this will not remove the bond information from the connected device!
+ *
+ * The success callback will be called after the device get disconnected,
+ * when the {@link BluetoothDevice#getBondState()} changes to {@link BluetoothDevice#BOND_NONE}.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @return The request.
+ */
+ @NonNull
+ protected Request removeBond() {
+ return Request.removeBond().setManager(this);
+ }
+
+ @MainThread
+ private boolean internalRemoveBond() {
+ final BluetoothDevice device = mBluetoothDevice;
+ if (device == null)
+ return false;
+
+ log(Log.VERBOSE, "Removing bond information...");
+
+ if (device.getBondState() == BluetoothDevice.BOND_NONE) {
+ log(Log.WARN, "Device is not bonded");
+ mRequest.notifySuccess(device);
+ mGattCallback.nextRequest(true);
+ return true;
+ }
+
+ /*
+ * There is a removeBond() method in BluetoothDevice class but for now it's hidden.
+ * We will call it using reflections.
+ */
+ try {
+ //noinspection JavaReflectionMemberAccess
+ final Method removeBond = device.getClass().getMethod("removeBond");
+ log(Log.DEBUG, "device.removeBond() (hidden)");
+ return (Boolean) removeBond.invoke(device);
+ } catch (final Exception e) {
+ Log.w(TAG, "An exception occurred while removing bond", e);
+ }
+ return false;
+ }
+
+ /**
+ * 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
+ * To remove the callback, disable notifications using
+ * {@link #disableNotifications(BluetoothGattCharacteristic)}.
+ *
+ * @param characteristic characteristic to bind the callback with. If null, the returned
+ * callback will not be null, but will not be used.
+ * @return The callback.
+ */
+ @MainThread
+ @NonNull
+ protected ValueChangedCallback setNotificationCallback(@Nullable final BluetoothGattCharacteristic characteristic) {
+ ValueChangedCallback callback = mNotificationCallbacks.get(characteristic);
+ if (callback == null) {
+ callback = new ValueChangedCallback();
+ if (characteristic != null) {
+ mNotificationCallbacks.put(characteristic, callback);
+ }
+ }
+ return callback.free();
+ }
+
+ /**
+ * Returns the callback that is registered for value changes (indications) of given
+ * characteristic. After assigning the notifications callback, indications must be
+ * enabled using {@link #enableIndications(BluetoothGattCharacteristic)}.
+ * This applies also when they were already enabled on the remote side.
+ *
+ * To remove the callback, disable indications using
+ * {@link #disableIndications(BluetoothGattCharacteristic)}.
+ *
+ * @param characteristic characteristic to bind the callback with. If null, the returned
+ * callback will not be null, but will not be used.
+ * @return The callback.
+ */
+ @NonNull
+ protected ValueChangedCallback setIndicationCallback(@Nullable final BluetoothGattCharacteristic characteristic) {
+ return setNotificationCallback(characteristic);
+ }
+
+ /**
+ * Sets a one-time callback that will be notified when the value of the given characteristic
+ * changes. This is a blocking request, so the next request will be executed after the
+ * notification was received.
+ *
+ * If {@link WaitForValueChangedRequest#merge(DataMerger)} was used, the whole message will be
+ * completed before the callback is notified.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic that value is expect to change.
+ * @return The callback.
+ */
+ @NonNull
+ protected WaitForValueChangedRequest waitForNotification(@NonNull final BluetoothGattCharacteristic characteristic) {
+ return Request.newWaitForNotificationRequest(characteristic).setManager(this);
+ }
+
+ /**
+ * Sets a one-time callback that will be notified when the value of the given characteristic
+ * changes. This is a blocking request, so the next request will be executed after the
+ * indication was received.
+ *
+ * If {@link WaitForValueChangedRequest#merge(DataMerger)} was used, the whole message will be
+ * completed before the callback is notified.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic that value is expect to change.
+ * @return The callback.
+ */
+ @NonNull
+ protected WaitForValueChangedRequest waitForIndication(@NonNull final BluetoothGattCharacteristic characteristic) {
+ return Request.newWaitForIndicationRequest(characteristic).setManager(this);
+ }
+
+ /**
+ * Enables notifications on given characteristic.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to be enabled.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest enableNotifications(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return Request.newEnableNotificationsRequest(characteristic).setManager(this);
+ }
+
+ @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;
+ }
+
+ /**
+ * Disables notifications on given characteristic.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to be disabled.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest disableNotifications(@Nullable final BluetoothGattCharacteristic characteristic) {
+ return Request.newDisableNotificationsRequest(characteristic).setManager(this);
+ }
+
+ @MainThread
+ private boolean internalDisableNotifications(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() + ", false)");
+ gatt.setCharacteristicNotification(characteristic, false);
+
+ descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
+ log(Log.VERBOSE, "Disabling notifications and indications for " + characteristic.getUuid());
+ log(Log.DEBUG, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x00-00)");
+ return internalWriteDescriptorWorkaround(descriptor);
+ }
+ return false;
+ }
+
+ /**
+ * Enables indications on given characteristic.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to be enabled.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest enableIndications(@Nullable final BluetoothGattCharacteristic characteristic) {
+ return Request.newEnableIndicationsRequest(characteristic).setManager(this);
+ }
+
+ @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;
+ }
+
+ /**
+ * Disables indications on given characteristic.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to be disabled.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest disableIndications(@Nullable final BluetoothGattCharacteristic characteristic) {
+ return Request.newDisableIndicationsRequest(characteristic).setManager(this);
+ }
+
+ @MainThread
+ private boolean internalDisableIndications(final BluetoothGattCharacteristic characteristic) {
+ // This writes exactly the same settings so do not duplicate code.
+ return internalDisableNotifications(characteristic);
+ }
+
+ /**
+ * Returns the Client Characteristic Config Descriptor if the characteristic has the
+ * required property. It may return null if the CCCD is not there.
+ *
+ * @param characteristic the characteristic to look the CCCD in.
+ * @param requiredProperty the required property: {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY}
+ * or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}.
+ * @return The CCC descriptor or null if characteristic is null, if it doesn't have the
+ * required property, or if the CCCD is missing.
+ */
+ private static BluetoothGattDescriptor getCccd(@Nullable final BluetoothGattCharacteristic characteristic,
+ final int requiredProperty) {
+ if (characteristic == null)
+ return null;
+
+ // Check characteristic property
+ final int properties = characteristic.getProperties();
+ if ((properties & requiredProperty) == 0)
+ return null;
+
+ return characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
+ }
+
+ /**
+ * Sends the read request to the given characteristic.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to read.
+ * @return The request.
+ */
+ @NonNull
+ protected ReadRequest readCharacteristic(@Nullable final BluetoothGattCharacteristic characteristic) {
+ return Request.newReadRequest(characteristic).setManager(this);
+ }
+
+ @MainThread
+ private boolean internalReadCharacteristic(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_READ) == 0)
+ return false;
+
+ log(Log.VERBOSE, "Reading characteristic " + characteristic.getUuid());
+ log(Log.DEBUG, "gatt.readCharacteristic(" + characteristic.getUuid() + ")");
+ return gatt.readCharacteristic(characteristic);
+ }
+
+ /**
+ * Writes the given data to the characteristic. The write type is taken from the characteristic.
+ *
+ * Use {@link WriteRequest#split() split()} or
+ * {@link WriteRequest#split(DataSplitter) split(DataSplitter)} on the returned
+ * {@link WriteRequest} if data should be automatically split into multiple packets.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to write to.
+ * @param data data to be written to the characteristic.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest writeCharacteristic(@Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final Data data) {
+ return Request.newWriteRequest(characteristic, data != null ? data.getValue() : null)
+ .setManager(this);
+ }
+
+ /**
+ * Writes the given data to the characteristic. The write type is taken from the characteristic.
+ *
+ * Use {@link WriteRequest#split() split()} or
+ * {@link WriteRequest#split(DataSplitter) split(ValueSplitter)} on the returned
+ * {@link WriteRequest} if data should be automatically split into multiple packets.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to write to.
+ * @param data data to be written to the characteristic.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest writeCharacteristic(@Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final byte[] data) {
+ return Request.newWriteRequest(characteristic, data).setManager(this);
+ }
+
+ /**
+ * Writes at most length bytes from offset at given data to the characteristic.
+ * The write type is taken from the characteristic.
+ *
+ * Use {@link WriteRequest#split() split()} or
+ * {@link WriteRequest#split(DataSplitter) split(ValueSplitter)} on the returned
+ * {@link WriteRequest} if data should be automatically split into multiple packets.
+ * If the characteristic is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param characteristic the characteristic to write to.
+ * @param data data to be written to the characteristic.
+ * @param offset index of the first byte to be sent.
+ * @param length number of bytes to be sent.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest writeCharacteristic(@Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final byte[] data, final int offset, final int length) {
+ return Request.newWriteRequest(characteristic, data, offset, length).setManager(this);
+ }
+
+ @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);
+ }
+
+ /**
+ * Sends the read request to the given descriptor.
+ * If the descriptor is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param descriptor the descriptor to read.
+ * @return The request.
+ */
+ @NonNull
+ protected ReadRequest readDescriptor(@Nullable final BluetoothGattDescriptor descriptor) {
+ return Request.newReadRequest(descriptor).setManager(this);
+ }
+
+ @MainThread
+ private boolean internalReadDescriptor(final BluetoothGattDescriptor descriptor) {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || descriptor == null || !mConnected)
+ return false;
+
+ log(Log.VERBOSE, "Reading descriptor " + descriptor.getUuid());
+ log(Log.DEBUG, "gatt.readDescriptor(" + descriptor.getUuid() + ")");
+ return gatt.readDescriptor(descriptor);
+ }
+
+ /**
+ * Writes the given data to the descriptor.
+ *
+ * Use {@link WriteRequest#split() split()} or
+ * {@link WriteRequest#split(DataSplitter) split(ValueSplitter)} on the returned
+ * {@link WriteRequest} if data should be automatically split into multiple packets.
+ * If the descriptor is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param descriptor the descriptor to write to.
+ * @param data data to be written to the descriptor.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest writeDescriptor(@Nullable final BluetoothGattDescriptor descriptor,
+ @Nullable final Data data) {
+ return Request.newWriteRequest(descriptor, data != null ? data.getValue() : null)
+ .setManager(this);
+ }
+
+ /**
+ * Writes the given data to the descriptor.
+ *
+ * Use {@link WriteRequest#split() split()} or
+ * {@link WriteRequest#split(DataSplitter) split(ValueSplitter)} on the returned
+ * {@link WriteRequest} if data should be automatically split into multiple packets.
+ * If the descriptor is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param descriptor the descriptor to write to.
+ * @param data data to be written to the descriptor.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest writeDescriptor(@Nullable final BluetoothGattDescriptor descriptor,
+ @Nullable final byte[] data) {
+ return Request.newWriteRequest(descriptor, data).setManager(this);
+ }
+
+ /**
+ * Writes at most length bytes from offset at given data to the descriptor.
+ *
+ * Use {@link WriteRequest#split() split()} or
+ * {@link WriteRequest#split(DataSplitter) split(ValueSplitter)} on the returned
+ * {@link WriteRequest} if data should be automatically split into multiple packets.
+ * If the descriptor is null, the {@link Request#fail(FailCallback) fail(FailCallback)}
+ * callback will be called.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param descriptor the descriptor to write to.
+ * @param data data to be written to the descriptor.
+ * @param offset index of the first byte to be sent.
+ * @param length number of bytes to be sent.
+ * @return The request.
+ */
+ @NonNull
+ protected WriteRequest writeDescriptor(@Nullable final BluetoothGattDescriptor descriptor,
+ @Nullable final byte[] data, final int offset,
+ final int length) {
+ return Request.newWriteRequest(descriptor, data, offset, length).setManager(this);
+ }
+
+ @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);
+ }
+
+ /**
+ * Creates an atomic request queue. The requests from the queue will be executed in order.
+ * This is useful when more then one thread may add requests and you want some of them to
+ * be executed together.
+ *
+ * @return The request.
+ */
+ @NonNull
+ protected RequestQueue beginAtomicRequestQueue() {
+ return new RequestQueue().setManager(this);
+ }
+
+ /**
+ * Begins the Reliable Write sub-procedure. Requests that need to be performed reliably
+ * should be enqueued with {@link ReliableWriteRequest#add(Operation)} instead of using
+ * {@link Request#enqueue()}. The library will verify all Write operations and will
+ * automatically abort the Reliable Write procedure when the returned data mismatch with the
+ * data sent. When all requests enqueued in the {@link ReliableWriteRequest} were completed,
+ * the Reliable Write will be automatically executed.
+ *
+ * Long Write will not work when Reliable Write is in progress. The library will make sure
+ * that {@link WriteRequest#split()} was called for all {@link WriteRequest} packets, had
+ * they not been assigned other splitter.
+ *
+ * At least one Write operation must be executed before executing or aborting, otherwise the
+ * {@link GattError#GATT_INVALID_OFFSET} error will be reported. Because of that, enqueueing
+ * a {@link ReliableWriteRequest} without any operations does nothing.
+ *
+ * Example of usage:
+ *
+ * See:
+ * https://android.googlesource.com/platform/frameworks/base/+/942aebc95924ab1e7ea1e92aaf4e7fc45f695a6c%5E%21/#F0
+ *
+ * @param descriptor the descriptor to be written
+ * @return the result of {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor)}
+ */
+ @MainThread
+ private boolean internalWriteDescriptorWorkaround(final BluetoothGattDescriptor descriptor) {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || descriptor == null || !mConnected)
+ return false;
+
+ final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();
+ final int originalWriteType = parentCharacteristic.getWriteType();
+ parentCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+ final boolean result = gatt.writeDescriptor(descriptor);
+ parentCharacteristic.setWriteType(originalWriteType);
+ return result;
+ }
+
+ /**
+ * Requests new MTU. On Android Lollipop or newer it will send the MTU request to the connected
+ * device. On older versions of Android the
+ * {@link MtuCallback#onMtuChanged(BluetoothDevice, int)} set with
+ * {@link MtuRequest#with(MtuCallback)} will be called with current MTU value.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @return The request.
+ */
+ protected MtuRequest requestMtu(@IntRange(from = 23, to = 517) final int mtu) {
+ return Request.newMtuRequest(mtu).setManager(this);
+ }
+
+ /**
+ * Returns the current MTU (Maximum Transfer Unit). MTU specifies the maximum number of bytes
+ * that can be sent in a single write operation. 3 bytes are used for internal purposes,
+ * so the maximum size is MTU-3. The value will changed only if requested with
+ * {@link #requestMtu(int)} and a successful callback is received. If the peripheral requests
+ * MTU change, the {@link BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)}
+ * callback is not invoked, therefor the returned MTU value will not be correct.
+ * Use {@link android.bluetooth.BluetoothGattServerCallback#onMtuChanged(BluetoothDevice, int)}
+ * to get the callback with right value requested from the peripheral side.
+ *
+ * @return the current MTU value. Default to 23.
+ */
+ @IntRange(from = 23, to = 517)
+ protected int getMtu() {
+ return mMtu;
+ }
+
+ /**
+ * This method overrides the MTU value. Use it only when the peripheral has changed MTU and you
+ * received the
+ * {@link android.bluetooth.BluetoothGattServerCallback#onMtuChanged(BluetoothDevice, int)}
+ * callback. If you want to set MTU as a master, use {@link #requestMtu(int)} instead.
+ *
+ * @param mtu the MTU value set by the peripheral.
+ */
+ protected void overrideMtu(@IntRange(from = 23, to = 517) final int mtu) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mMtu = mtu;
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @MainThread
+ private boolean internalRequestMtu(@IntRange(from = 23, to = 517) final int mtu) {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ log(Log.VERBOSE, "Requesting new MTU...");
+ log(Log.DEBUG, "gatt.requestMtu(" + mtu + ")");
+ return gatt.requestMtu(mtu);
+ }
+
+ /**
+ * Requests the new connection priority. Acceptable values are:
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param priority one of: {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH},
+ * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED},
+ * {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
+ * @return The request.
+ */
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ protected ConnectionPriorityRequest requestConnectionPriority(
+ @ConnectionPriority final int priority) {
+ return Request.newConnectionPriorityRequest(priority).setManager(this);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @MainThread
+ private boolean internalRequestConnectionPriority(@ConnectionPriority final int priority) {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ String text, priorityText;
+ switch (priority) {
+ case ConnectionPriorityRequest.CONNECTION_PRIORITY_HIGH:
+ text = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
+ "HIGH (11.25–15ms, 0, 20s)" : "HIGH (7.5–10ms, 0, 20s)";
+ priorityText = "HIGH";
+ break;
+ case ConnectionPriorityRequest.CONNECTION_PRIORITY_LOW_POWER:
+ text = "LOW POWER (100–125ms, 2, 20s)";
+ priorityText = "LOW POWER";
+ break;
+ default:
+ case ConnectionPriorityRequest.CONNECTION_PRIORITY_BALANCED:
+ text = "BALANCED (30–50ms, 0, 20s)";
+ priorityText = "BALANCED";
+ break;
+ }
+ log(Log.VERBOSE, "Requesting connection priority: " + text + "...");
+ log(Log.DEBUG, "gatt.requestConnectionPriority(" + priorityText + ")");
+ return gatt.requestConnectionPriority(priority);
+ }
+
+ /**
+ * Enqueues a request to set the preferred PHY.
+ *
+ * PHY LE 2M and PHY LE Coded are supported only on Android Oreo or newer.
+ * You may safely request other PHYs on older platforms, but you will get PHY LE 1M
+ * as TX and RX PHY in the callback.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param txPhy preferred transmitter PHY. Bitwise OR of any of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK},
+ * and {@link PhyRequest#PHY_LE_CODED_MASK}.
+ * @param rxPhy preferred receiver PHY. Bitwise OR of any of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK},
+ * and {@link PhyRequest#PHY_LE_CODED_MASK}.
+ * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
+ * of {@link PhyRequest#PHY_OPTION_NO_PREFERRED},
+ * {@link PhyRequest#PHY_OPTION_S2} or {@link PhyRequest#PHY_OPTION_S8}.
+ * @return The request.
+ */
+ protected PhyRequest setPreferredPhy(@PhyMask final int txPhy, @PhyMask final int rxPhy,
+ @PhyOption final int phyOptions) {
+ return Request.newSetPreferredPhyRequest(txPhy, rxPhy, phyOptions).setManager(this);
+ }
+
+ @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;
+ }
+
+ /**
+ * Reads the current PHY for this connections.
+ *
+ * PHY LE 2M and PHY LE Coded are supported only on Android Oreo or newer.
+ * You may safely read PHY on older platforms, but you will get PHY LE 1M as TX and RX PHY
+ * in the callback.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @return The request.
+ */
+ protected PhyRequest readPhy() {
+ return Request.newReadPhyRequest().setManager(this);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ @MainThread
+ private boolean internalReadPhy() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ log(Log.VERBOSE, "Reading PHY...");
+ log(Log.DEBUG, "gatt.readPhy()");
+ gatt.readPhy();
+ return true;
+ }
+
+ /**
+ * Reads the current RSSI (Received Signal Strength Indication).
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @return The request.
+ */
+ protected ReadRssiRequest readRssi() {
+ return Request.newReadRssiRequest().setManager(this);
+ }
+
+ @MainThread
+ private boolean internalReadRssi() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ log(Log.VERBOSE, "Reading remote RSSI...");
+ log(Log.DEBUG, "gatt.readRemoteRssi()");
+ return gatt.readRemoteRssi();
+ }
+
+ /**
+ * Refreshes the device cache. As the {@link BluetoothGatt#refresh()} method is not in the
+ * public API (it's hidden, and on Android P it is on a light gray list) it is called
+ * using reflections and may be removed in some future Android release or on some devices.
+ *
+ * There is no callback indicating when the cache has been cleared. This library assumes
+ * some time and waits. After the delay, it will start service discovery and clear the
+ * task queue. When the service discovery finishes, the
+ * {@link BleManager.BleManagerGattCallback#isRequiredServiceSupported(BluetoothGatt)} and
+ * {@link BleManager.BleManagerGattCallback#isOptionalServiceSupported(BluetoothGatt)} will
+ * be called and the initialization will be performed as if the device has just connected.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @return The request.
+ */
+ @SuppressWarnings("JavadocReference")
+ protected Request refreshDeviceCache() {
+ return Request.newRefreshCacheRequest().setManager(this);
+ }
+
+ /**
+ * Clears the device cache.
+ */
+ @SuppressWarnings("JavaReflectionMemberAccess")
+ @MainThread
+ private boolean internalRefreshDeviceCache() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null) // no need to be connected
+ return false;
+
+ log(Log.VERBOSE, "Refreshing device cache...");
+ log(Log.DEBUG, "gatt.refresh() (hidden)");
+ /*
+ * There is a refresh() method in BluetoothGatt class but for now it's hidden.
+ * We will call it using reflections.
+ */
+ try {
+ final Method refresh = gatt.getClass().getMethod("refresh");
+ return (Boolean) refresh.invoke(gatt);
+ } catch (final Exception e) {
+ Log.w(TAG, "An exception occurred while refreshing device", e);
+ log(Log.WARN, "gatt.refresh() method not found");
+ }
+ return false;
+ }
+
+ /**
+ * Enqueues a sleep operation with given duration. The next request will be performed after
+ * at least given number of milliseconds.
+ *
+ * The returned request must be either enqueued using {@link Request#enqueue()} for
+ * asynchronous use, or awaited using await() in synchronous execution.
+ *
+ * @param delay the delay in milliseconds.
+ * @return The request.
+ */
+ protected SleepRequest sleep(@IntRange(from = 0) final long delay) {
+ return Request.newSleepRequest(delay).setManager(this);
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * Cancels all the enqueued operations. The one currently executed will be finished.
+ */
+ protected void clearQueue() {
+ final BleManagerGattCallback callback = mGattCallback;
+ if (callback != null) {
+ callback.cancelQueue();
+ }
+ }
+
+ private void runOnUiThread(final Runnable runnable) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ mHandler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ @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);
+ }
+ }
+
+ @SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})
+ protected abstract class BleManagerGattCallback extends MainThreadBluetoothGattCallback {
+ private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change";
+ private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services";
+ private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information";
+ private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic";
+ private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic";
+ private final static String ERROR_READ_DESCRIPTOR = "Error on reading descriptor";
+ private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor";
+ private final static String ERROR_MTU_REQUEST = "Error on mtu request";
+ private final static String ERROR_CONNECTION_PRIORITY_REQUEST = "Error on connection priority request";
+ private final static String ERROR_READ_RSSI = "Error on RSSI read";
+ private final static String ERROR_READ_PHY = "Error on PHY read";
+ private final static String ERROR_PHY_UPDATE = "Error on PHY update";
+ private final static String ERROR_RELIABLE_WRITE = "Error on Execute Reliable Write";
+
+ private final Deque
+ * This method is called when the services has been discovered and the device is supported
+ * (has required service).
+ *
+ * @param gatt the gatt device with services discovered
+ * @return The queue of requests.
+ * @deprecated Use {@link #initialize()} instead.
+ */
+ @Deprecated
+ protected Deque
+ * This method is called from the main thread when the services has been discovered and
+ * the device is supported (has required service).
+ *
+ * Remember to call {@link Request#enqueue()} for each request.
+ *
+ * A sample initialization should look like this:
+ * If this callback is invoked while a reliable write transaction is
+ * in progress, the value of the characteristic represents the value
+ * reported by the remote device. An application should compare this
+ * value to the desired value to be written. If the values don't match,
+ * the application must abort the reliable write transaction.
+ *
+ * @param gatt GATT client
+ * @param characteristic Characteristic that was written to the associated remote device.
+ * @deprecated Use {@link WriteRequest#done(SuccessCallback)} instead.
+ */
+ @Deprecated
+ protected void onCharacteristicWrite(@NonNull final BluetoothGatt gatt,
+ @NonNull final BluetoothGattCharacteristic characteristic) {
+ // do nothing
+ }
+
+ /**
+ * Callback reporting the result of a descriptor read operation.
+ *
+ * @param gatt GATT client
+ * @param descriptor Descriptor that was read from the associated remote device.
+ * @deprecated Use {@link ReadRequest#with(DataReceivedCallback)} instead.
+ */
+ @Deprecated
+ protected void onDescriptorRead(@NonNull final BluetoothGatt gatt,
+ @NonNull final BluetoothGattDescriptor descriptor) {
+ // do nothing
+ }
+
+ /**
+ * Callback indicating the result of a descriptor write operation.
+ * If this callback is invoked while a reliable write transaction is in progress,
+ * the value of the characteristic represents the value reported by the remote device.
+ * An application should compare this value to the desired value to be written.
+ * If the values don't match, the application must abort the reliable write transaction.
+ *
+ * @param gatt GATT client
+ * @param descriptor Descriptor that was written to the associated remote device.
+ * @deprecated Use {@link WriteRequest} and {@link SuccessCallback} instead.
+ */
+ @Deprecated
+ protected void onDescriptorWrite(@NonNull final BluetoothGatt gatt,
+ @NonNull final BluetoothGattDescriptor descriptor) {
+ // do nothing
+ }
+
+ /**
+ * Callback reporting the value of Battery Level characteristic which could have
+ * been received by Read or Notify operations.
+ *
+ * This method will not be called if {@link #readBatteryLevel()} and
+ * {@link #enableBatteryLevelNotifications()} were overridden.
+ *
+ * Keeping this file as is (and {@link BleManager} as well) will allow to quickly update it when
+ * an update is posted here.
+ */
+@SuppressWarnings({"DeprecatedIsStillUsed", "unused"})
+public interface BleManagerCallbacks {
+
+ /**
+ * Called when the Android device started connecting to given device.
+ * The {@link #onDeviceConnected(BluetoothDevice)} will be called when the device is connected,
+ * or {@link #onError(BluetoothDevice, String, int)} in case of error.
+ *
+ * @param device the device that got connected.
+ */
+ void onDeviceConnecting(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when the device has been connected. This does not mean that the application may start
+ * communication.
+ * A service discovery will be handled automatically after this call. Service discovery
+ * may ends up with calling {@link #onServicesDiscovered(BluetoothDevice, boolean)} or
+ * {@link #onDeviceNotSupported(BluetoothDevice)} if required services have not been found.
+ *
+ * @param device the device that got connected.
+ */
+ void onDeviceConnected(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when user initialized disconnection.
+ *
+ * @param device the device that gets disconnecting.
+ */
+ void onDeviceDisconnecting(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when the device has disconnected (when the callback returned
+ * {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)} with state
+ * DISCONNECTED), but ONLY if the {@link BleManager#shouldAutoConnect()} method returned false
+ * for this device when it was connecting.
+ * Otherwise the {@link #onLinkLossOccurred(BluetoothDevice)} method will be called instead.
+ *
+ * @param device the device that got disconnected.
+ */
+ void onDeviceDisconnected(@NonNull final BluetoothDevice device);
+
+ /**
+ * This callback is invoked when the Ble Manager lost connection to a device that has been
+ * connected with autoConnect option (see {@link BleManager#shouldAutoConnect()}.
+ * Otherwise a {@link #onDeviceDisconnected(BluetoothDevice)} method will be called on such
+ * event.
+ *
+ * @param device the device that got disconnected due to a link loss.
+ */
+ void onLinkLossOccurred(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when service discovery has finished and primary services has been found.
+ * This method is not called if the primary, mandatory services were not found during service
+ * discovery. For example in the Blood Pressure Monitor, a Blood Pressure service is a
+ * primary service and Intermediate Cuff Pressure service is a optional secondary service.
+ * Existence of battery service is not notified by this call.
+ *
+ * After successful service discovery the service will initialize all services.
+ * The {@link #onDeviceReady(BluetoothDevice)} method will be called when the initialization
+ * is complete.
+ *
+ * @param device the device which services got disconnected.
+ * @param optionalServicesFound if
+ * This method may return true only if an activity is bound to the service (to display the
+ * information to the user), always (e.g. if critical battery level is reported using
+ * notifications) or never, if such information is not important or the manager wants to
+ * control Battery Level notifications on its own.
+ *
+ * @param device the target device.
+ * @return True to enabled battery level notifications after connecting to the device,
+ * false otherwise.
+ * @deprecated Use
+ *
+ * The {@link #done(SuccessCallback)} callback will be called after the device is ready, that is
+ * when it is connected, the services were discovered, the required services were found and the
+ * initialization queue set in {@link BleManager.BleManagerGattCallback#initialize()} is complete
+ * (without or with errors).
+ */
+@SuppressWarnings({"WeakerAccess", "unused", "deprecation"})
+public class ConnectRequest extends TimeoutableRequest {
+ @NonNull
+ private BluetoothDevice device;
+ @PhyMask
+ private int preferredPhy;
+ @IntRange(from = 0)
+ private int attempt = 0, retries = 0;
+ @IntRange(from = 0)
+ private int delay = 0;
+ private boolean autoConnect = false;
+
+ ConnectRequest(@NonNull final Type type, @NonNull final BluetoothDevice device) {
+ super(type);
+ this.device = device;
+ this.preferredPhy = PhyRequest.PHY_LE_1M_MASK;
+ }
+
+ @NonNull
+ @Override
+ ConnectRequest setManager(@NonNull final BleManager manager) {
+ super.setManager(manager);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public ConnectRequest timeout(@IntRange(from = 0) final long timeout) {
+ super.timeout(timeout);
+ return this;
+ }
+
+ /**
+ * Use to set a completion callback. The callback will be invoked when the operation has
+ * finished successfully unless {@link #await()} or its variant was used, in which case this
+ * callback will be ignored.
+ *
+ * The done callback will also be called when one or more of initialization requests has
+ * failed due to a reason other than disconnect event. This is because
+ * {@link BleManagerCallbacks#onDeviceReady(BluetoothDevice)} is called no matter
+ * if the requests succeeded, or not. Set failure callbacks to initialization requests
+ * to get information about failures.
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ @Override
+ public ConnectRequest done(@NonNull final SuccessCallback callback) {
+ super.done(callback);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public ConnectRequest fail(@NonNull final FailCallback callback) {
+ super.fail(callback);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public ConnectRequest invalid(@NonNull final InvalidRequestCallback callback) {
+ super.invalid(callback);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public ConnectRequest before(@NonNull final BeforeCallback callback) {
+ super.before(callback);
+ return this;
+ }
+
+ /**
+ * Sets an optional retry count. The BleManager will do that many attempts to connect to the
+ * device in case of an error. The library will NOT retry if the device is not reachable,
+ * that is when the 30 sec. timeout occurs. In that case the app should scan before
+ * connecting, to make sure the target is in range.
+ *
+ * @param count how many times should the BleManager retry to connect.
+ * @return The request.
+ * @see #retry(int, int)
+ */
+ public ConnectRequest retry(@IntRange(from = 0) final int count) {
+ this.retries = count;
+ this.delay = 0;
+ return this;
+ }
+
+ /**
+ * Sets an optional retry count and a delay that the process will wait before each connection
+ * attempt. The library will NOT retry if the device is not reachable, that is when the 30 sec.
+ * timeout occurs. In that case the app should scan before connecting, to make sure the
+ * target is in range.
+ *
+ * @param count how many times should the BleManager retry to connect.
+ * @param delay the delay between each connection attempt, in milliseconds.
+ * The real delay will be 200 ms longer than specified, as
+ * {@link BluetoothGatt#clone()} is estimated to last
+ * {@link BleManager#internalConnect(BluetoothDevice, ConnectRequest) 200 ms}.
+ * @return The request.
+ * @see #retry(int)
+ */
+ public ConnectRequest retry(@IntRange(from = 0) final int count,
+ @IntRange(from = 0) final int delay) {
+ this.retries = count;
+ this.delay = delay;
+ return this;
+ }
+
+ /**
+ * This method replaces the {@link BleManager#shouldAutoConnect()} method.
+ *
+ * Sets whether to connect to the remote device just once (false) or to add the address to
+ * white list of devices that will be automatically connect as soon as they become available
+ * (true). In the latter case, if Bluetooth adapter is enabled, Android scans periodically
+ * for devices from the white list and, if an advertising packet is received from such, it tries
+ * to connect to it. When the connection is lost, the system will keep trying to reconnect to
+ * it. If method is called with parameter set to true, and the connection to the device is
+ * lost, the {@link BleManagerCallbacks#onLinkLossOccurred(BluetoothDevice)} callback is
+ * called instead of {@link BleManagerCallbacks#onDeviceDisconnected(BluetoothDevice)}.
+ *
+ * This feature works much better on newer Android phone models and may have issues on older
+ * phones.
+ *
+ * This method should only be used with bonded devices, as otherwise the device may change
+ * it's address. It will however work also with non-bonded devices with private static address.
+ * A connection attempt to a non-bonded device with private resolvable address will fail.
+ *
+ * The first connection to a device will always be created with autoConnect flag to false
+ * (see {@link BluetoothDevice#connectGatt(Context, boolean, BluetoothGattCallback)}). This is
+ * to make it quick as the user most probably waits for a quick response. If autoConnect is
+ * used (true), the following connections will be done using {@link BluetoothGatt#connect()},
+ * which forces the autoConnect parameter to true.
+ *
+ * @param autoConnect true to use autoConnect feature on the second and following connections.
+ * The first connection is always done with autoConnect parameter equal to
+ * false, to make it faster and allow to timeout it the device is unreachable.
+ * Default value is false.
+ * @return The request.
+ */
+ public ConnectRequest useAutoConnect(final boolean autoConnect) {
+ this.autoConnect = autoConnect;
+ return this;
+ }
+
+ /**
+ * Sets the preferred PHY used for connection. Th value should be a bitmask composed of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK} or
+ * {@link PhyRequest#PHY_LE_CODED_MASK}.
+ *
+ * Different PHYs are available only on more recent devices with Android 8+.
+ * Check {@link BluetoothAdapter#isLe2MPhySupported()} and
+ * {@link BluetoothAdapter#isLeCodedPhySupported()} if required PHYs are supported by this
+ * Android device. The default PHY is {@link PhyRequest#PHY_LE_1M_MASK}.
+ *
+ * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK},
+ * and {@link PhyRequest#PHY_LE_CODED_MASK}. This option does not take effect
+ * if {@code autoConnect} is set to true.
+ * @return The request.
+ */
+ public ConnectRequest usePreferredPhy(@PhyMask final int phy) {
+ this.preferredPhy = phy;
+ return this;
+ }
+
+ @NonNull
+ public BluetoothDevice getDevice() {
+ return device;
+ }
+
+ @PhyMask
+ int getPreferredPhy() {
+ return preferredPhy;
+ }
+
+ boolean canRetry() {
+ if (retries > 0) {
+ retries -= 1;
+ return true;
+ }
+ return false;
+ }
+
+ boolean isFirstAttempt() {
+ return attempt++ == 0;
+ }
+
+ @IntRange(from = 0)
+ int getRetryDelay() {
+ return delay;
+ }
+
+ boolean shouldAutoConnect() {
+ return autoConnect;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java b/app/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java
new file mode 100644
index 0000000..9dabbd0
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Build;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import no.nordicsemi.android.ble.annotation.ConnectionPriority;
+import no.nordicsemi.android.ble.callback.BeforeCallback;
+import no.nordicsemi.android.ble.callback.ConnectionPriorityCallback;
+import no.nordicsemi.android.ble.callback.FailCallback;
+import no.nordicsemi.android.ble.callback.InvalidRequestCallback;
+import no.nordicsemi.android.ble.callback.SuccessCallback;
+import no.nordicsemi.android.ble.exception.BluetoothDisabledException;
+import no.nordicsemi.android.ble.exception.DeviceDisconnectedException;
+import no.nordicsemi.android.ble.exception.InvalidRequestException;
+import no.nordicsemi.android.ble.exception.RequestFailedException;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public final class ConnectionPriorityRequest extends SimpleValueRequest
+ * Interval: 30 - 50 ms, latency: 0, supervision timeout: 20 sec.
+ */
+ public static final int CONNECTION_PRIORITY_BALANCED = 0;
+
+ /**
+ * Connection parameter update - Request a high priority, low latency connection.
+ * An application should only request high priority connection parameters to transfer
+ * large amounts of data over LE quickly. Once the transfer is complete, the application
+ * should request {@link #CONNECTION_PRIORITY_BALANCED} connection parameters
+ * to reduce energy use.
+ *
+ * Interval: 11.25 - 15 ms (Android 6+) or 7.5 - 10 ms (Android 4.3 - 5.1),
+ * latency: 0, supervision timeout: 20 sec.
+ */
+ public static final int CONNECTION_PRIORITY_HIGH = 1;
+
+ /**
+ * Connection parameter update - Request low power, reduced data rate connection parameters.
+ *
+ * Interval: 100 - 125 ms, latency: 2, supervision timeout: 20 sec.
+ */
+ public static final int CONNECTION_PRIORITY_LOW_POWER = 2;
+
+ private final int value;
+
+ ConnectionPriorityRequest(@NonNull final Type type, @ConnectionPriority int priority) {
+ super(type);
+ if (priority < 0 || priority > 2)
+ priority = CONNECTION_PRIORITY_BALANCED;
+ this.value = priority;
+ }
+
+ @NonNull
+ @Override
+ ConnectionPriorityRequest setManager(@NonNull final BleManager manager) {
+ super.setManager(manager);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public ConnectionPriorityRequest done(@NonNull final SuccessCallback callback) {
+ super.done(callback);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public ConnectionPriorityRequest fail(@NonNull final FailCallback callback) {
+ super.fail(callback);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public ConnectionPriorityRequest invalid(@NonNull final InvalidRequestCallback callback) {
+ super.invalid(callback);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public ConnectionPriorityRequest before(@NonNull final BeforeCallback callback) {
+ super.before(callback);
+ return this;
+ }
+
+ @RequiresApi(value = Build.VERSION_CODES.O)
+ @Override
+ @NonNull
+ public ConnectionPriorityRequest with(@NonNull final ConnectionPriorityCallback callback) {
+ // The BluetoothGattCallback#onConnectionUpdated callback was introduced in Android Oreo.
+ super.with(callback);
+ return this;
+ }
+
+ @RequiresApi(value = Build.VERSION_CODES.O)
+ @NonNull
+ @Override
+ public
+ * The device will disconnect after calling this method. The success callback will be called
+ * after the device got disconnected, when the {@link BluetoothDevice#getBondState()} changes
+ * to {@link BluetoothDevice#BOND_NONE}.
+ *
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#removeBond()} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static SimpleRequest removeBond() {
+ return new SimpleRequest(Type.REMOVE_BOND);
+ }
+
+ /**
+ * Creates new Read Characteristic request. The request will not be executed if given
+ * characteristic is null or does not have READ property.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to be read.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#readCharacteristic(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static ReadRequest newReadRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new ReadRequest(Type.READ, characteristic);
+ }
+
+ /**
+ * Creates new Write Characteristic request. The request will not be executed if given
+ * characteristic is null or does not have WRITE property.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to be written.
+ * @param value value to be written. The array is copied into another buffer so it's
+ * safe to reuse the array again.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#writeCharacteristic(BluetoothGattCharacteristic, byte[])} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newWriteRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final byte[] value) {
+ return new WriteRequest(Type.WRITE, characteristic, value, 0,
+ value != null ? value.length : 0,
+ characteristic != null ?
+ characteristic.getWriteType() :
+ BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+ }
+
+ /**
+ * Creates new Write Characteristic request. The request will not be executed if given
+ * characteristic is null or does not have WRITE property.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to be written.
+ * @param value value to be written. The array is copied into another buffer so it's
+ * safe to reuse the array again.
+ * @param writeType write type to be used, one of
+ * {@link BluetoothGattCharacteristic#WRITE_TYPE_DEFAULT},
+ * {@link BluetoothGattCharacteristic#WRITE_TYPE_NO_RESPONSE}.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#writeCharacteristic(BluetoothGattCharacteristic, Data)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newWriteRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final byte[] value, @WriteType final int writeType) {
+ return new WriteRequest(Type.WRITE, characteristic, value, 0,
+ value != null ? value.length : 0, writeType);
+ }
+
+ /**
+ * Creates new Write Characteristic request. The request will not be executed if given
+ * characteristic is null or does not have WRITE property.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to be written.
+ * @param value value to be written. The array is copied into another buffer so it's
+ * safe to reuse the array again.
+ * @param offset the offset from which value has to be copied.
+ * @param length number of bytes to be copied from the value buffer.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#writeCharacteristic(BluetoothGattCharacteristic, byte[], int, int)}
+ * instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newWriteRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final byte[] value,
+ @IntRange(from = 0) final int offset, @IntRange(from = 0) final int length) {
+ return new WriteRequest(Type.WRITE, characteristic, value, offset, length,
+ characteristic != null ?
+ characteristic.getWriteType() :
+ BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+ }
+
+ /**
+ * Creates new Write Characteristic request. The request will not be executed if given
+ * characteristic is null or does not have WRITE property.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to be written.
+ * @param value value to be written. The array is copied into another buffer so it's
+ * safe to reuse the array again.
+ * @param offset the offset from which value has to be copied.
+ * @param length number of bytes to be copied from the value buffer.
+ * @param writeType write type to be used, one of
+ * {@link BluetoothGattCharacteristic#WRITE_TYPE_DEFAULT},
+ * {@link BluetoothGattCharacteristic#WRITE_TYPE_NO_RESPONSE} or
+ * {@link BluetoothGattCharacteristic#WRITE_TYPE_SIGNED}.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#writeCharacteristic(BluetoothGattCharacteristic, byte[], int, int)}
+ * instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newWriteRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic,
+ @Nullable final byte[] value,
+ @IntRange(from = 0) final int offset, @IntRange(from = 0) final int length,
+ @WriteType final int writeType) {
+ return new WriteRequest(Type.WRITE, characteristic, value, offset, length, writeType);
+ }
+
+ /**
+ * Creates new Read Descriptor request. The request will not be executed if given descriptor
+ * is null. After the operation is complete a proper callback will be invoked.
+ *
+ * @param descriptor descriptor to be read.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#readDescriptor(BluetoothGattDescriptor)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static ReadRequest newReadRequest(@Nullable final BluetoothGattDescriptor descriptor) {
+ return new ReadRequest(Type.READ_DESCRIPTOR, descriptor);
+ }
+
+ /**
+ * Creates new Write Descriptor request. The request will not be executed if given descriptor
+ * is null. After the operation is complete a proper callback will be invoked.
+ *
+ * @param descriptor descriptor to be written.
+ * @param value value to be written. The array is copied into another buffer so it's safe
+ * to reuse the array again.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#writeDescriptor(BluetoothGattDescriptor, byte[])} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newWriteRequest(@Nullable final BluetoothGattDescriptor descriptor,
+ @Nullable final byte[] value) {
+ return new WriteRequest(Type.WRITE_DESCRIPTOR, descriptor, value, 0,
+ value != null ? value.length : 0);
+ }
+
+ /**
+ * Creates new Write Descriptor request. The request will not be executed if given descriptor
+ * is null. After the operation is complete a proper callback will be invoked.
+ *
+ * @param descriptor descriptor to be written.
+ * @param value value to be written. The array is copied into another buffer so it's safe
+ * to reuse the array again.
+ * @param offset the offset from which value has to be copied.
+ * @param length number of bytes to be copied from the value buffer.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#writeDescriptor(BluetoothGattDescriptor, byte[], int, int)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newWriteRequest(
+ @Nullable final BluetoothGattDescriptor descriptor,
+ @Nullable final byte[] value,
+ @IntRange(from = 0) final int offset, @IntRange(from = 0) final int length) {
+ return new WriteRequest(Type.WRITE_DESCRIPTOR, descriptor, value, offset, length);
+ }
+
+ /**
+ * Creates new Reliable Write request. All operations that need to be executed
+ * reliably should be enqueued inside the returned request before enqueuing it in the
+ * BleManager. The library will automatically verify the data sent
+ *
+ * @return The new request.
+ */
+ @NonNull
+ static ReliableWriteRequest newReliableWriteRequest() {
+ return new ReliableWriteRequest();
+ }
+
+ /**
+ * Creates new Begin Reliable Write request.
+ *
+ * @return The new request.
+ */
+ @NonNull
+ static SimpleRequest newBeginReliableWriteRequest() {
+ return new SimpleRequest(Type.BEGIN_RELIABLE_WRITE);
+ }
+
+ /**
+ * Executes Reliable Write sub-procedure. At lease one Write Request must be performed
+ * before the Reliable Write is to be executed, otherwise error
+ * {@link no.nordicsemi.android.ble.error.GattError#GATT_INVALID_OFFSET} will be returned.
+ *
+ * @return The new request.
+ */
+ @NonNull
+ static SimpleRequest newExecuteReliableWriteRequest() {
+ return new SimpleRequest(Type.EXECUTE_RELIABLE_WRITE);
+ }
+
+ /**
+ * Aborts Reliable Write sub-procedure. All write requests performed during Reliable Write will
+ * be cancelled. At lease one Write Request must be performed before the Reliable Write
+ * is to be executed, otherwise error
+ * {@link no.nordicsemi.android.ble.error.GattError#GATT_INVALID_OFFSET} will be returned.
+ *
+ * @return The new request.
+ */
+ @NonNull
+ static SimpleRequest newAbortReliableWriteRequest() {
+ return new SimpleRequest(Type.ABORT_RELIABLE_WRITE);
+ }
+
+ /**
+ * Creates new Enable Notification request. The request will not be executed if given
+ * characteristic is null, does not have NOTIFY property or the CCCD.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to have notifications enabled.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#enableNotifications(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newEnableNotificationsRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new WriteRequest(Type.ENABLE_NOTIFICATIONS, characteristic);
+ }
+
+ /**
+ * Creates new Disable Notification request. The request will not be executed if given
+ * characteristic is null, does not have NOTIFY property or the CCCD.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to have notifications disabled.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#disableNotifications(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newDisableNotificationsRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new WriteRequest(Type.DISABLE_NOTIFICATIONS, characteristic);
+ }
+
+ /**
+ * Creates new Enable Indications request. The request will not be executed if given
+ * characteristic is null, does not have INDICATE property or the CCCD.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to have indications enabled.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#enableIndications(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newEnableIndicationsRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new WriteRequest(Type.ENABLE_INDICATIONS, characteristic);
+ }
+
+ /**
+ * Creates new Disable Indications request. The request will not be executed if given
+ * characteristic is null, does not have INDICATE property or the CCCD.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * @param characteristic characteristic to have indications disabled.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#disableNotifications(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WriteRequest newDisableIndicationsRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new WriteRequest(Type.DISABLE_INDICATIONS, characteristic);
+ }
+
+ /**
+ * Creates new Wait For Notification request. The request will not be executed if given
+ * characteristic is null, does not have NOTIFY property or the CCCD.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * If the notification should be triggered by another operation (for example writing an
+ * op code), set it with {@link WaitForValueChangedRequest#trigger(Operation)}.
+ *
+ * @param characteristic characteristic from which a notification should be received.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#waitForNotification(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WaitForValueChangedRequest newWaitForNotificationRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new WaitForValueChangedRequest(Type.WAIT_FOR_NOTIFICATION, characteristic);
+ }
+
+ /**
+ * Creates new Wait For Indication request. The request will not be executed if given
+ * characteristic is null, does not have INDICATE property or the CCCD.
+ * After the operation is complete a proper callback will be invoked.
+ *
+ * If the indication should be triggered by another operation (for example writing an
+ * op code), set it with {@link WaitForValueChangedRequest#trigger(Operation)}.
+ *
+ * @param characteristic characteristic from which a notification should be received.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#waitForIndication(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static WaitForValueChangedRequest newWaitForIndicationRequest(
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ return new WaitForValueChangedRequest(Type.WAIT_FOR_INDICATION, characteristic);
+ }
+
+ /**
+ * Creates new Read Battery Level request. The first found Battery Level characteristic value
+ * from the first found Battery Service. If any of them is not found, or the characteristic
+ * does not have the READ property this operation will not execute.
+ *
+ * @return The new request.
+ * @deprecated Use {@link #newReadRequest(BluetoothGattCharacteristic)} with
+ * BatteryLevelDataCallback from Android BLE Common Library instead.
+ */
+ @NonNull
+ @Deprecated
+ public static ReadRequest newReadBatteryLevelRequest() {
+ return new ReadRequest(Type.READ_BATTERY_LEVEL);
+ }
+
+ /**
+ * Creates new Enable Notifications on the first found Battery Level characteristic from the
+ * first found Battery Service. If any of them is not found, or the characteristic does not
+ * have the NOTIFY property this operation will not execute.
+ *
+ * @return The new request.
+ * @deprecated Use {@link #newEnableNotificationsRequest(BluetoothGattCharacteristic)} with
+ * BatteryLevelDataCallback from Android BLE Common Library instead.
+ */
+ @NonNull
+ @Deprecated
+ public static WriteRequest newEnableBatteryLevelNotificationsRequest() {
+ return new WriteRequest(Type.ENABLE_BATTERY_LEVEL_NOTIFICATIONS);
+ }
+
+ /**
+ * Creates new Disable Notifications on the first found Battery Level characteristic from the
+ * first found Battery Service. If any of them is not found, or the characteristic does not
+ * have the NOTIFY property this operation will not execute.
+ *
+ * @return The new request.
+ * @deprecated Use {@link #newDisableNotificationsRequest(BluetoothGattCharacteristic)} instead.
+ */
+ @NonNull
+ @Deprecated
+ public static WriteRequest newDisableBatteryLevelNotificationsRequest() {
+ return new WriteRequest(Type.DISABLE_BATTERY_LEVEL_NOTIFICATIONS);
+ }
+
+ /**
+ * Creates new Enable Indications on Service Changed characteristic. It is a NOOP if such
+ * characteristic does not exist in the Generic Attribute service.
+ * It is required to enable those notifications on bonded devices on older Android versions to
+ * be informed about attributes changes.
+ * Android 7+ (or 6+) handles this automatically and no action is required.
+ *
+ * @return The new request.
+ */
+ @NonNull
+ static WriteRequest newEnableServiceChangedIndicationsRequest() {
+ return new WriteRequest(Type.ENABLE_SERVICE_CHANGED_INDICATIONS);
+ }
+
+ /**
+ * Requests new MTU (Maximum Transfer Unit). This is only supported on Android Lollipop or newer.
+ * On older platforms the request will enqueue, but will fail to execute and
+ * {@link #fail(FailCallback)} callback will be called.
+ * The target device may reject requested value and set a smaller MTU.
+ *
+ * @param mtu the new MTU. Acceptable values are <23, 517>.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#requestMtu(int)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static MtuRequest newMtuRequest(@IntRange(from = 23, to = 517) final int mtu) {
+ return new MtuRequest(Type.REQUEST_MTU, mtu);
+ }
+
+ /**
+ * Requests the new connection priority. Acceptable values are:
+ *
+ * PHY LE 2M and PHY LE Coded are supported only on Android Oreo or newer.
+ * You may safely request other PHYs on older platforms, but the request will not be executed
+ * and you will get PHY LE 1M as TX and RX PHY in the callback.
+ *
+ * @param txPhy preferred transmitter PHY. Bitwise OR of any of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK},
+ * and {@link PhyRequest#PHY_LE_CODED_MASK}.
+ * @param rxPhy preferred receiver PHY. Bitwise OR of any of
+ * {@link PhyRequest#PHY_LE_1M_MASK}, {@link PhyRequest#PHY_LE_2M_MASK},
+ * and {@link PhyRequest#PHY_LE_CODED_MASK}.
+ * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
+ * of {@link PhyRequest#PHY_OPTION_NO_PREFERRED},
+ * {@link PhyRequest#PHY_OPTION_S2} or {@link PhyRequest#PHY_OPTION_S8}.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#setPreferredPhy(int, int, int)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static PhyRequest newSetPreferredPhyRequest(@PhyMask final int txPhy,
+ @PhyMask final int rxPhy,
+ @PhyOption final int phyOptions) {
+ return new PhyRequest(Type.SET_PREFERRED_PHY, txPhy, rxPhy, phyOptions);
+ }
+
+ /**
+ * Reads the current PHY for this connections.
+ *
+ * PHY LE 2M and PHY LE Coded are supported only on Android Oreo or newer.
+ * You may safely read PHY on older platforms, but the request will not be executed
+ * and you will get PHY LE 1M as TX and RX PHY in the callback.
+ *
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#readPhy()} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static PhyRequest newReadPhyRequest() {
+ return new PhyRequest(Type.READ_PHY);
+ }
+
+ /**
+ * Reads the current RSSI (Received Signal Strength Indication).
+ *
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#readRssi()} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static ReadRssiRequest newReadRssiRequest() {
+ return new ReadRssiRequest(Type.READ_RSSI);
+ }
+
+ /**
+ * Refreshes the device cache. As the {@link BluetoothGatt#refresh()} method is not in the
+ * public API (it's hidden, and on Android P it is on a light gray list) it is called
+ * using reflections and may be removed in some future Android release or on some devices.
+ *
+ * There is no callback indicating when the cache has been cleared. This library assumes
+ * some time and waits. After the delay, it will start service discovery and clear the
+ * task queue. When the service discovery finishes, the
+ * {@link BleManager.BleManagerGattCallback#isRequiredServiceSupported(BluetoothGatt)} and
+ * {@link BleManager.BleManagerGattCallback#isOptionalServiceSupported(BluetoothGatt)} will
+ * be called and the initialization will be performed as if the device has just connected.
+ *
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#refreshDeviceCache()} instead.
+ */
+ @Deprecated
+ @SuppressWarnings("JavadocReference")
+ @NonNull
+ public static SimpleRequest newRefreshCacheRequest() {
+ return new SimpleRequest(Type.REFRESH_CACHE);
+ }
+
+ /**
+ * Creates new Sleep request that will postpone next request for given number of milliseconds.
+ *
+ * @param delay the delay in milliseconds.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#sleep(long)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static SleepRequest newSleepRequest(@IntRange(from = 0) final long delay) {
+ return new SleepRequest(Type.SLEEP, delay);
+ }
+
+ /**
+ * Use to set a completion callback. The callback will be invoked when the operation has
+ * finished successfully unless the request was executed synchronously, in which case this
+ * callback will be ignored.
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ public Request done(@NonNull final SuccessCallback callback) {
+ this.successCallback = callback;
+ return this;
+ }
+
+ /**
+ * Use to set a callback that will be called in case the request has failed.
+ * If the target device wasn't set before executing this request
+ * ({@link BleManager#connect(BluetoothDevice)} was never called), the
+ * {@link #invalid(InvalidRequestCallback)} will be used instead, as the
+ * {@link BluetoothDevice} is not known.
+ *
+ * This callback will be ignored if request was executed synchronously, in which case
+ * the error will be returned as an exception.
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ public Request fail(@NonNull final FailCallback callback) {
+ this.failCallback = callback;
+ return this;
+ }
+
+ /**
+ * Used to set internal callback what will be executed before the request is executed.
+ *
+ * @param callback the callback.
+ */
+ void internalBefore(@NonNull final BeforeCallback callback) {
+ this.internalBeforeCallback = callback;
+ }
+
+ /**
+ * Used to set internal success callback. The callback will be notified in case the request
+ * has completed.
+ *
+ * @param callback the callback.
+ */
+ void internalSuccess(@NonNull final SuccessCallback callback) {
+ this.internalSuccessCallback = callback;
+ }
+
+
+ /**
+ * Used to set internal fail callback. The callback will be notified in case the request
+ * has failed.
+ *
+ * @param callback the callback.
+ */
+ void internalFail(@NonNull final FailCallback callback) {
+ this.internalFailCallback = callback;
+ }
+
+ /**
+ * Use to set a callback that will be called in case the request was invalid, for example
+ * called before the device was connected.
+ * This callback will be ignored if request was executed synchronously, in which case
+ * the error will be returned as an exception.
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ public Request invalid(@NonNull final InvalidRequestCallback callback) {
+ this.invalidRequestCallback = callback;
+ return this;
+ }
+
+ /**
+ * Sets a callback that will be executed before the execution of this operation starts.
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ public Request before(@NonNull final BeforeCallback callback) {
+ this.beforeCallback = callback;
+ return this;
+ }
+
+ /**
+ * Enqueues the request for asynchronous execution.
+ */
+ public void enqueue() {
+ manager.enqueue(this);
+ }
+
+ void notifyStarted(@NonNull final BluetoothDevice device) {
+ if (beforeCallback != null)
+ beforeCallback.onRequestStarted(device);
+ if (internalBeforeCallback != null)
+ internalBeforeCallback.onRequestStarted(device);
+ }
+
+ void notifySuccess(@NonNull final BluetoothDevice device) {
+ if (!finished) {
+ finished = true;
+
+ if (successCallback != null)
+ successCallback.onRequestCompleted(device);
+ if (internalSuccessCallback != null)
+ internalSuccessCallback.onRequestCompleted(device);
+ }
+ }
+
+ void notifyFail(@NonNull final BluetoothDevice device, final int status) {
+ if (!finished) {
+ finished = true;
+
+ if (failCallback != null)
+ failCallback.onRequestFailed(device, status);
+ if (internalFailCallback != null)
+ internalFailCallback.onRequestFailed(device, status);
+ }
+ }
+
+ void notifyInvalidRequest() {
+ if (!finished) {
+ finished = true;
+
+ if (invalidRequestCallback != null)
+ invalidRequestCallback.onInvalidRequest();
+ }
+ }
+
+ /**
+ * Asserts that the synchronous method was not called from the UI thread.
+ *
+ * @throws IllegalStateException when called from a UI thread.
+ */
+ static void assertNotMainThread() throws IllegalStateException {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("Cannot execute synchronous operation from the UI thread.");
+ }
+ }
+
+ final class RequestCallback implements SuccessCallback, FailCallback, InvalidRequestCallback {
+ final static int REASON_REQUEST_INVALID = -1000000;
+ int status = BluetoothGatt.GATT_SUCCESS;
+
+ @Override
+ public void onRequestCompleted(@NonNull final BluetoothDevice device) {
+ syncLock.open();
+ }
+
+ @Override
+ public void onRequestFailed(@NonNull final BluetoothDevice device, final int status) {
+ this.status = status;
+ syncLock.open();
+ }
+
+ @Override
+ public void onInvalidRequest() {
+ this.status = REASON_REQUEST_INVALID;
+ syncLock.open();
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ boolean isSuccess() {
+ return this.status == BluetoothGatt.GATT_SUCCESS;
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/RequestQueue.java b/app/src/main/java/no/nordicsemi/android/ble/RequestQueue.java
new file mode 100644
index 0000000..6d97ab2
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/RequestQueue.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.callback.BeforeCallback;
+import no.nordicsemi.android.ble.callback.FailCallback;
+import no.nordicsemi.android.ble.callback.InvalidRequestCallback;
+import no.nordicsemi.android.ble.callback.SuccessCallback;
+
+@SuppressWarnings("WeakerAccess")
+public class RequestQueue extends SimpleRequest {
+ /**
+ * A list of operations that will be executed together.
+ */
+ @NonNull
+ private final Queue
+ * It is safe to call this method in {@link Request#done(SuccessCallback)} or
+ * {@link Request#fail(FailCallback)} callback;
+ */
+ public void cancelQueue() {
+ requests.clear();
+ }
+
+ /**
+ * Returns the next {@link Request} to be enqueued.
+ *
+ * @return the next request.
+ */
+ @Nullable
+ Request getNext() {
+ try {
+ return requests.remove();
+ // poll() may also throw an exception
+ // See: https://github.com/NordicSemiconductor/Android-BLE-Library/issues/37
+ } catch (final Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns whether there are more operations to be executed.
+ *
+ * @return true, if not all operations were completed.
+ */
+ boolean hasMore() {
+ return !requests.isEmpty();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/SimpleRequest.java b/app/src/main/java/no/nordicsemi/android/ble/SimpleRequest.java
new file mode 100644
index 0000000..23359d7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/SimpleRequest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.callback.FailCallback;
+import no.nordicsemi.android.ble.callback.SuccessCallback;
+import no.nordicsemi.android.ble.exception.BluetoothDisabledException;
+import no.nordicsemi.android.ble.exception.DeviceDisconnectedException;
+import no.nordicsemi.android.ble.exception.InvalidRequestException;
+import no.nordicsemi.android.ble.exception.RequestFailedException;
+
+/**
+ * A request that requires a {@link android.bluetooth.BluetoothGattCallback callback} or can't
+ * have timeout for any other reason. This class defines the {@link #await()} method.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class SimpleRequest extends Request {
+
+ SimpleRequest(@NonNull final Type type) {
+ super(type);
+ }
+
+ SimpleRequest(@NonNull final Type type,
+ @Nullable final BluetoothGattCharacteristic characteristic) {
+ super(type, characteristic);
+ }
+
+ SimpleRequest(@NonNull final Type type,
+ @Nullable final BluetoothGattDescriptor descriptor) {
+ super(type, descriptor);
+ }
+
+ /**
+ * Synchronously waits until the request is done.
+ *
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)}
+ * will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @throws RequestFailedException thrown when the BLE request finished with status other
+ * than {@link BluetoothGatt#GATT_SUCCESS}.
+ * @throws IllegalStateException thrown when you try to call this method from the main
+ * (UI) thread.
+ * @throws DeviceDisconnectedException thrown when the device disconnected before the request
+ * was completed.
+ * @throws BluetoothDisabledException thrown when the Bluetooth adapter has been disabled.
+ * @throws InvalidRequestException thrown when the request was called before the device was
+ * connected at least once (unknown device).
+ */
+ public final void await() throws RequestFailedException, DeviceDisconnectedException,
+ BluetoothDisabledException, InvalidRequestException {
+ assertNotMainThread();
+
+ final SuccessCallback sc = successCallback;
+ final FailCallback fc = failCallback;
+ try {
+ syncLock.close();
+ final RequestCallback callback = new RequestCallback();
+ done(callback).fail(callback).invalid(callback).enqueue();
+
+ syncLock.block();
+ if (!callback.isSuccess()) {
+ if (callback.status == FailCallback.REASON_DEVICE_DISCONNECTED) {
+ throw new DeviceDisconnectedException();
+ }
+ if (callback.status == FailCallback.REASON_BLUETOOTH_DISABLED) {
+ throw new BluetoothDisabledException();
+ }
+ if (callback.status == RequestCallback.REASON_REQUEST_INVALID) {
+ throw new InvalidRequestException(this);
+ }
+ throw new RequestFailedException(this, callback.status);
+ }
+ } finally {
+ successCallback = sc;
+ failCallback = fc;
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/SimpleValueRequest.java b/app/src/main/java/no/nordicsemi/android/ble/SimpleValueRequest.java
new file mode 100644
index 0000000..7115a67
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/SimpleValueRequest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.callback.FailCallback;
+import no.nordicsemi.android.ble.callback.SuccessCallback;
+import no.nordicsemi.android.ble.exception.BluetoothDisabledException;
+import no.nordicsemi.android.ble.exception.DeviceDisconnectedException;
+import no.nordicsemi.android.ble.exception.InvalidRequestException;
+import no.nordicsemi.android.ble.exception.RequestFailedException;
+
+/**
+ * A value request that requires a {@link android.bluetooth.BluetoothGattCallback callback} or
+ * can't have timeout for any other reason. This class defines the {@link #await()} methods.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class SimpleValueRequest
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)} and
+ * {@link #with(T)} will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param response the response object.
+ * @param
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)} and
+ * {@link #with(T)} will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param responseClass the response class. This class will be instantiate, therefore it has
+ * to have a default constructor.
+ * @return The response with a response.
+ * @throws RequestFailedException thrown when the BLE request finished with status other
+ * than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}.
+ * @throws IllegalStateException thrown when you try to call this method from the main
+ * (UI) thread.
+ * @throws IllegalArgumentException thrown when the response class could not be instantiated.
+ * @throws DeviceDisconnectedException thrown when the device disconnected before the request
+ * was completed.
+ * @throws BluetoothDisabledException thrown when the Bluetooth adapter is disabled.
+ * @throws InvalidRequestException thrown when the request was called before the device was
+ * connected at least once (unknown device).
+ * @see #await(Object)
+ */
+ @NonNull
+ public
+ * Use {@link #timeout(long)} to set the maximum time the manager should wait until the device
+ * is ready. When the timeout occurs, the request will fail with
+ * {@link FailCallback#REASON_TIMEOUT} and the device will get disconnected.
+ */
+ @Override
+ public final void enqueue() {
+ super.enqueue();
+ }
+
+ /**
+ * Enqueues the request for asynchronous execution.
+ *
+ * When the timeout occurs, the request will fail with {@link FailCallback#REASON_TIMEOUT}
+ * and the device will get disconnected.
+ *
+ * @param timeout the request timeout in milliseconds, 0 to disable timeout. This value will
+ * override one set in {@link #timeout(long)}.
+ * @deprecated Use {@link #timeout(long)} and {@link #enqueue()} instead.
+ */
+ @Deprecated
+ public final void enqueue(@IntRange(from = 0) final long timeout) {
+ timeout(timeout).enqueue();
+ }
+
+ /**
+ * Synchronously waits until the request is done.
+ *
+ * Use {@link #timeout(long)} to set the maximum time the manager should wait until the request
+ * is ready. When the timeout occurs, the {@link InterruptedException} will be thrown.
+ *
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)}
+ * will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @throws RequestFailedException thrown when the BLE request finished with status other
+ * than {@link BluetoothGatt#GATT_SUCCESS}.
+ * @throws InterruptedException thrown if the timeout occurred before the request has
+ * finished.
+ * @throws IllegalStateException thrown when you try to call this method from the main
+ * (UI) thread.
+ * @throws DeviceDisconnectedException thrown when the device disconnected before the request
+ * was completed.
+ * @throws BluetoothDisabledException thrown when the Bluetooth adapter has been disabled.
+ * @throws InvalidRequestException thrown when the request was called before the device was
+ * connected at least once (unknown device).
+ * @see #enqueue()
+ */
+ public final void await() throws RequestFailedException, DeviceDisconnectedException,
+ BluetoothDisabledException, InvalidRequestException, InterruptedException {
+ assertNotMainThread();
+
+ final SuccessCallback sc = successCallback;
+ final FailCallback fc = failCallback;
+ try {
+ syncLock.close();
+ final RequestCallback callback = new RequestCallback();
+ done(callback).fail(callback).invalid(callback).enqueue();
+
+ if (!syncLock.block(timeout)) {
+ throw new InterruptedException();
+ }
+ if (!callback.isSuccess()) {
+ if (callback.status == FailCallback.REASON_DEVICE_DISCONNECTED) {
+ throw new DeviceDisconnectedException();
+ }
+ if (callback.status == FailCallback.REASON_BLUETOOTH_DISABLED) {
+ throw new BluetoothDisabledException();
+ }
+ if (callback.status == RequestCallback.REASON_REQUEST_INVALID) {
+ throw new InvalidRequestException(this);
+ }
+ throw new RequestFailedException(this, callback.status);
+ }
+ } finally {
+ successCallback = sc;
+ failCallback = fc;
+ }
+ }
+
+ /**
+ * Synchronously waits, for as most as the given number of milliseconds, until the request
+ * is ready.
+ *
+ * When the timeout occurs, the {@link InterruptedException} will be thrown.
+ *
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)}
+ * will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param timeout optional timeout in milliseconds, 0 to disable timeout. This will
+ * override the timeout set using {@link #timeout(long)}.
+ * @throws RequestFailedException thrown when the BLE request finished with status other
+ * than {@link BluetoothGatt#GATT_SUCCESS}.
+ * @throws InterruptedException thrown if the timeout occurred before the request has
+ * finished.
+ * @throws IllegalStateException thrown when you try to call this method from the main
+ * (UI) thread.
+ * @throws DeviceDisconnectedException thrown when the device disconnected before the request
+ * was completed.
+ * @throws BluetoothDisabledException thrown when the Bluetooth adapter has been disabled.
+ * @throws InvalidRequestException thrown when the request was called before the device was
+ * connected at least once (unknown device).
+ * @deprecated Use {@link #timeout(long)} and {@link #await()} instead.
+ */
+ @Deprecated
+ public final void await(@IntRange(from = 0) final long timeout) throws RequestFailedException,
+ InterruptedException, DeviceDisconnectedException, BluetoothDisabledException,
+ InvalidRequestException {
+ timeout(timeout).await();
+ }
+
+ @Override
+ void notifyStarted(@NonNull final BluetoothDevice device) {
+ if (timeout > 0L) {
+ timeoutCallback = () -> {
+ timeoutCallback = null;
+ if (!finished) {
+ notifyFail(device, FailCallback.REASON_TIMEOUT);
+ timeoutHandler.onRequestTimeout(this);
+ }
+ };
+ handler.postDelayed(timeoutCallback, timeout);
+ }
+ super.notifyStarted(device);
+ }
+
+ @Override
+ void notifySuccess(@NonNull final BluetoothDevice device) {
+ if (!finished) {
+ handler.removeCallbacks(timeoutCallback);
+ timeoutCallback = null;
+ }
+ super.notifySuccess(device);
+ }
+
+ @Override
+ void notifyFail(@NonNull final BluetoothDevice device, final int status) {
+ if (!finished) {
+ handler.removeCallbacks(timeoutCallback);
+ timeoutCallback = null;
+ }
+ super.notifyFail(device, status);
+ }
+
+ @Override
+ void notifyInvalidRequest() {
+ if (!finished) {
+ handler.removeCallbacks(timeoutCallback);
+ timeoutCallback = null;
+ }
+ super.notifyInvalidRequest();
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/TimeoutableValueRequest.java b/app/src/main/java/no/nordicsemi/android/ble/TimeoutableValueRequest.java
new file mode 100644
index 0000000..1ffcf47
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/TimeoutableValueRequest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.callback.FailCallback;
+import no.nordicsemi.android.ble.callback.SuccessCallback;
+import no.nordicsemi.android.ble.exception.BluetoothDisabledException;
+import no.nordicsemi.android.ble.exception.DeviceDisconnectedException;
+import no.nordicsemi.android.ble.exception.InvalidRequestException;
+import no.nordicsemi.android.ble.exception.RequestFailedException;
+
+/**
+ * A value request that requires a {@link android.bluetooth.BluetoothGattCallback callback} or
+ * can't have timeout for any other reason. This class defines the {@link #await()} methods.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class TimeoutableValueRequest
+ * When the timeout, set with {@link #timeout(long)} occurs, the {@link InterruptedException}
+ * will be thrown.
+ *
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)} and
+ * {@link #with(E)} will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param response the response object.
+ * @param
+ * Callbacks set using {@link #done(SuccessCallback)} and {@link #fail(FailCallback)} and
+ * {@link #with(T)} will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param responseClass the response class. This class will be instantiate, therefore it has
+ * to have a default constructor.
+ * @return The response with a response.
+ * @throws RequestFailedException thrown when the BLE request finished with status other
+ * than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}.
+ * @throws InterruptedException thrown if the timeout occurred before the request has
+ * finished.
+ * @throws IllegalStateException thrown when you try to call this method from the main
+ * (UI) thread.
+ * @throws IllegalArgumentException thrown when the response class could not be instantiated.
+ * @throws DeviceDisconnectedException thrown when the device disconnected before the request
+ * was completed.
+ * @throws BluetoothDisabledException thrown when the Bluetooth adapter is disabled.
+ * @throws InvalidRequestException thrown when the request was called before the device was
+ * connected at least once (unknown device).
+ * @see #await(Object)
+ */
+ @NonNull
+ public
+ * Callbacks set using {@link #done(SuccessCallback)}, {@link #fail(FailCallback)} and
+ * {@link #with(E)} will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param responseClass the response class. This class will be instantiate, therefore it has
+ * to have a default constructor.
+ * @param timeout optional timeout in milliseconds. This value will override one set
+ * in {@link #timeout(long)}.
+ * @param
+ * Callbacks set using {@link #done(SuccessCallback)}, {@link #fail(FailCallback)} and
+ * {@link #with(E)} will be ignored.
+ *
+ * This method may not be called from the main (UI) thread.
+ *
+ * @param response the response object.
+ * @param timeout optional timeout in milliseconds.
+ * @param
+ * On Android, requesting connection parameters is available since Android Lollipop using
+ * {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}. There are 3 options
+ * available: {@link android.bluetooth.BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER},
+ * {@link android.bluetooth.BluetoothGatt#CONNECTION_PRIORITY_BALANCED} and
+ * {@link android.bluetooth.BluetoothGatt#CONNECTION_PRIORITY_HIGH}. See
+ * {@link no.nordicsemi.android.ble.Request#newConnectionPriorityRequest(int)} for details.
+ *
+ * Until Android 8.0 Oreo, there was no callback indicating whether the change has succeeded,
+ * or not. Also, when a Central or Peripheral requested connection parameters change without
+ * explicit calling of this method, the application was not aware of it.
+ * Android Oreo added a hidden callback to {@link android.bluetooth.BluetoothGattCallback}
+ * notifying about connection parameters change. Those values will be reported with this callback.
+ */
+public interface ConnectionPriorityCallback {
+
+ /**
+ * Callback indicating the connection parameters were updated. Works on Android 8.0 Oreo or newer.
+ *
+ * @param device the target device.
+ * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
+ * 6 (7.5ms) to 3200 (4000ms).
+ * @param latency Slave latency for the connection in number of connection events. Valid range
+ * is from 0 to 499.
+ * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10
+ * (100 ms = 0.1s) to 3200 (32s).
+ */
+ void onConnectionUpdated(@NonNull final BluetoothDevice device,
+ @IntRange(from = 6, to = 3200) final int interval,
+ @IntRange(from = 0, to = 499) final int latency,
+ @IntRange(from = 10, to = 3200) final int timeout);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/DataReceivedCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/DataReceivedCallback.java
new file mode 100644
index 0000000..668a61c
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/DataReceivedCallback.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.data.Data;
+import no.nordicsemi.android.ble.data.DataMerger;
+
+public interface DataReceivedCallback {
+
+ /**
+ * Callback received each time the value was read or has changed using
+ * notifications or indications.
+ *
+ * @param device the target device.
+ * @param data the data received. If the {@link DataMerger} was used,
+ * this contains the merged result.
+ */
+ void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/DataSentCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/DataSentCallback.java
new file mode 100644
index 0000000..572b62a
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/DataSentCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.data.Data;
+import no.nordicsemi.android.ble.data.DataSplitter;
+
+public interface DataSentCallback {
+
+ /**
+ * Callback received each time the value was written to a characteristic or descriptor.
+ *
+ * @param device the target device.
+ * @param data the data sent. If the {@link DataSplitter} was used, this contains the full data.
+ */
+ void onDataSent(@NonNull final BluetoothDevice device, @NonNull final Data data);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/FailCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/FailCallback.java
new file mode 100644
index 0000000..4af98bb
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/FailCallback.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+
+public interface FailCallback {
+ int REASON_DEVICE_DISCONNECTED = -1;
+ int REASON_DEVICE_NOT_SUPPORTED = -2;
+ int REASON_NULL_ATTRIBUTE = -3;
+ int REASON_REQUEST_FAILED = -4;
+ int REASON_TIMEOUT = -5;
+ int REASON_VALIDATION = -6;
+ int REASON_BLUETOOTH_DISABLED = -100;
+
+ /**
+ * A callback invoked when the request has failed with status other than
+ * {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}.
+ *
+ * @param device target device.
+ * @param status error status code, one of BluetoothGatt#GATT_* constants or
+ * {@link #REASON_DEVICE_DISCONNECTED}, {@link #REASON_TIMEOUT},
+ * {@link #REASON_DEVICE_NOT_SUPPORTED} (only for Connect request),
+ * {@link #REASON_BLUETOOTH_DISABLED}, {@link #REASON_NULL_ATTRIBUTE},
+ * {@link #REASON_VALIDATION} or {@link #REASON_REQUEST_FAILED} (for other reason).
+ */
+ void onRequestFailed(@NonNull final BluetoothDevice device, final int status);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/InvalidRequestCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/InvalidRequestCallback.java
new file mode 100644
index 0000000..cb48051
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/InvalidRequestCallback.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+public interface InvalidRequestCallback {
+
+ /**
+ * A callback invoked when the request was invalid, for example when was called before the
+ * device was connected.
+ */
+ void onInvalidRequest();
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/MtuCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/MtuCallback.java
new file mode 100644
index 0000000..7879ce8
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/MtuCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+public interface MtuCallback {
+
+ /**
+ * Method called when the MTU request has finished with success. The MTU value may
+ * be different than requested one. The maximum packet size is 3 bytes less then MTU.
+ *
+ * @param device the target device.
+ * @param mtu the new MTU (Maximum Transfer Unit).
+ */
+ void onMtuChanged(@NonNull final BluetoothDevice device,
+ @IntRange(from = 23, to = 517) final int mtu);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/PhyCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/PhyCallback.java
new file mode 100644
index 0000000..8dc13d6
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/PhyCallback.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.annotation.PhyValue;
+
+@SuppressWarnings("unused")
+public interface PhyCallback {
+ /**
+ * Bluetooth LE 1M PHY. Used to refer to LE 1M Physical Channel for advertising, scanning or
+ * connection.
+ */
+ int PHY_LE_1M = 1;
+
+ /**
+ * Bluetooth LE 2M PHY. Used to refer to LE 2M Physical Channel for advertising, scanning or
+ * connection.
+ */
+ int PHY_LE_2M = 2;
+
+ /**
+ * Bluetooth LE Coded PHY. Used to refer to LE Coded Physical Channel for advertising, scanning
+ * or connection.
+ */
+ int PHY_LE_CODED = 3;
+
+ /**
+ * Method called when the PHY value has changed or was read.
+ *
+ * @param device the target device.
+ * @param txPhy the transmitter PHY in use. One of {@link #PHY_LE_1M},
+ * {@link #PHY_LE_2M}, and {@link #PHY_LE_CODED}.
+ * @param rxPhy the receiver PHY in use. One of {@link #PHY_LE_1M},
+ * {@link #PHY_LE_2M}, and {@link #PHY_LE_CODED}.
+ */
+ void onPhyChanged(@NonNull final BluetoothDevice device,
+ @PhyValue final int txPhy, @PhyValue final int rxPhy);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/ReadProgressCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/ReadProgressCallback.java
new file mode 100644
index 0000000..61d0e88
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/ReadProgressCallback.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.data.DataMerger;
+
+public interface ReadProgressCallback {
+
+ /**
+ * Callback received each time the value was read or has changed using notifications or
+ * indications when {@link DataMerger} was used.
+ *
+ * @param device the target device.
+ * @param data the last packet received.
+ * @param index the index of a packet that will be merged into a single Data.
+ */
+ void onPacketReceived(@NonNull final BluetoothDevice device,
+ @Nullable final byte[] data, @IntRange(from = 0) final int index);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/RssiCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/RssiCallback.java
new file mode 100644
index 0000000..9ca6873
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/RssiCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+public interface RssiCallback {
+
+ /**
+ * Method called when the RSSI value has been read.
+ *
+ * @param device the target device.
+ * @param rssi the current RSSI value, in dBm.
+ */
+ void onRssiRead(@NonNull final BluetoothDevice device,
+ @IntRange(from = -128, to = 20) final int rssi);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/SuccessCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/SuccessCallback.java
new file mode 100644
index 0000000..99367ad
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/SuccessCallback.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+
+public interface SuccessCallback {
+
+ /**
+ * A callback invoked when the request completed successfully.
+ *
+ * @param device the target device.
+ */
+ void onRequestCompleted(@NonNull final BluetoothDevice device);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/WriteProgressCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/WriteProgressCallback.java
new file mode 100644
index 0000000..d62752f
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/WriteProgressCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.data.DataSplitter;
+
+public interface WriteProgressCallback {
+
+ /**
+ * Callback called each time a packet has been sent when {@link DataSplitter} was used.
+ *
+ * @param device the target device.
+ * @param data the last packet sent.
+ * @param index the index of a packet that the initial Data was cut into.
+ */
+ void onPacketSent(@NonNull final BluetoothDevice device,
+ @Nullable final byte[] data, @IntRange(from = 0) final int index);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/profile/ProfileDataCallback.java b/app/src/main/java/no/nordicsemi/android/ble/callback/profile/ProfileDataCallback.java
new file mode 100644
index 0000000..a47c770
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/profile/ProfileDataCallback.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback.profile;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.callback.DataReceivedCallback;
+import no.nordicsemi.android.ble.data.Data;
+import no.nordicsemi.android.ble.data.DataMerger;
+
+@SuppressWarnings("unused")
+public interface ProfileDataCallback extends DataReceivedCallback {
+
+ /**
+ * Callback called when the data received do not conform to required scheme.
+ * @param device the target device.
+ * @param data the data received. If the {@link DataMerger} was used, this contains the
+ * merged result.
+ */
+ default void onInvalidDataReceived(@NonNull final BluetoothDevice device,
+ @NonNull final Data data) {
+ // ignore
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/callback/profile/ProfileReadResponse.java b/app/src/main/java/no/nordicsemi/android/ble/callback/profile/ProfileReadResponse.java
new file mode 100644
index 0000000..b26e025
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/callback/profile/ProfileReadResponse.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.callback.profile;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.data.Data;
+import no.nordicsemi.android.ble.response.ReadResponse;
+
+/**
+ * A response type for read requests with basic validation check.
+ * When read was requested as a synchronous call the {@link #isValid()} can be used to
+ * check if data were parsed successfully. Parsing method must call super methods on
+ * both {@link #onDataReceived(BluetoothDevice, Data)} and
+ * {@link #onInvalidDataReceived(BluetoothDevice, Data)} in order to make getters working properly.
+ *
+ * Check out profile data callbacks in the Android BLE Common Library for example of usage.
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class ProfileReadResponse extends ReadResponse implements ProfileDataCallback, Parcelable {
+ private boolean valid = true;
+
+ public ProfileReadResponse() {
+ // empty
+ }
+
+ @Override
+ public void onInvalidDataReceived(@NonNull final BluetoothDevice device,
+ @NonNull final Data data) {
+ this.valid = false;
+ }
+
+ /**
+ * Returns true if {@link #onInvalidDataReceived(BluetoothDevice, Data)} wasn't called.
+ *
+ * @return True, if profile data were valid, false if parsing error occurred.
+ */
+ public boolean isValid() {
+ return valid;
+ }
+
+ // Parcelable
+ protected ProfileReadResponse(final Parcel in) {
+ super(in);
+ valid = in.readByte() != 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (valid ? 1 : 0));
+ }
+
+ public static final Creator See {@link #getValue} for details.
+ *
+ * @param offset Offset at which the string value can be found.
+ * @return Cached value of the characteristic
+ */
+ @Nullable
+ public String getStringValue(@IntRange(from = 0) final int offset) {
+ if (mValue == null || offset > mValue.length)
+ return null;
+ final byte[] strBytes = new byte[mValue.length - offset];
+ for (int i = 0; i != (mValue.length - offset); ++i)
+ strBytes[i] = mValue[offset+i];
+ return new String(strBytes);
+ }
+
+ /**
+ * Returns the size of underlying byte array.
+ *
+ * @return Length of the data.
+ */
+ public int size() {
+ return mValue != null ? mValue.length : 0;
+ }
+
+ @Override
+ public String toString() {
+ if (size() == 0)
+ return "";
+
+ final char[] out = new char[mValue.length * 3 - 1];
+ for (int j = 0; j < mValue.length; j++) {
+ int v = mValue[j] & 0xFF;
+ out[j * 3] = HEX_ARRAY[v >>> 4];
+ out[j * 3 + 1] = HEX_ARRAY[v & 0x0F];
+ if (j != mValue.length - 1)
+ out[j * 3 + 2] = '-';
+ }
+ return "(0x) " + new String(out);
+ }
+
+ /**
+ * Returns a byte at the given offset from the byte array.
+ *
+ * @param offset Offset at which the byte value can be found.
+ * @return Cached value or null of offset exceeds value size.
+ */
+ @Nullable
+ public Byte getByte(@IntRange(from = 0) final int offset) {
+ if (offset + 1 > size()) return null;
+
+ return mValue[offset];
+ }
+
+ /**
+ * Returns an integer value from the byte array.
+ *
+ * The formatType parameter determines how the value
+ * is to be interpreted. For example, setting formatType to
+ * {@link #FORMAT_UINT16} specifies that the first two bytes of the
+ * value at the given offset are interpreted to generate the
+ * return value.
+ *
+ * @param formatType The format type used to interpret the value.
+ * @param offset Offset at which the integer value can be found.
+ * @return Cached value or null of offset exceeds value size.
+ */
+ @Nullable
+ public Integer getIntValue(@IntFormat final int formatType,
+ @IntRange(from = 0) final int offset) {
+ if ((offset + getTypeLen(formatType)) > size()) return null;
+
+ switch (formatType) {
+ case FORMAT_UINT8:
+ return unsignedByteToInt(mValue[offset]);
+
+ case FORMAT_UINT16:
+ return unsignedBytesToInt(mValue[offset], mValue[offset + 1]);
+
+ case FORMAT_UINT24:
+ return unsignedBytesToInt(mValue[offset], mValue[offset + 1],
+ mValue[offset + 2], (byte) 0);
+
+ case FORMAT_UINT32:
+ return unsignedBytesToInt(mValue[offset], mValue[offset + 1],
+ mValue[offset + 2], mValue[offset + 3]);
+
+ case FORMAT_SINT8:
+ return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8);
+
+ case FORMAT_SINT16:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset + 1]), 16);
+
+ case FORMAT_SINT24:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset + 1], mValue[offset + 2], (byte) 0), 24);
+
+ case FORMAT_SINT32:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]), 32);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a long value from the byte array.
+ * Only {@link #FORMAT_UINT32} and {@link #FORMAT_SINT32} are supported.
+ * The formatType parameter determines how the value
+ * is to be interpreted. For example, setting formatType to
+ * {@link #FORMAT_UINT32} specifies that the first four bytes of the
+ * value at the given offset are interpreted to generate the
+ * return value.
+ *
+ * @param formatType The format type used to interpret the value.
+ * @param offset Offset at which the integer value can be found.
+ * @return Cached value or null of offset exceeds value size.
+ */
+ @Nullable
+ public Long getLongValue(@LongFormat final int formatType,
+ @IntRange(from = 0) final int offset) {
+ if ((offset + getTypeLen(formatType)) > size()) return null;
+
+ switch (formatType) {
+ case FORMAT_SINT32:
+ return unsignedToSigned(unsignedBytesToLong(mValue[offset],
+ mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]), 32);
+
+ case FORMAT_UINT32:
+ return unsignedBytesToLong(mValue[offset], mValue[offset + 1],
+ mValue[offset + 2], mValue[offset + 3]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an float value from the given byte array.
+ *
+ * @param formatType The format type used to interpret the value.
+ * @param offset Offset at which the float value can be found.
+ * @return Cached value at a given offset or null if the requested offset exceeds the value size.
+ */
+ @Nullable
+ public Float getFloatValue(@FloatFormat final int formatType,
+ @IntRange(from = 0) final int offset) {
+ if ((offset + getTypeLen(formatType)) > size()) return null;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ if (mValue[offset + 1] == 0x07 && mValue[offset] == (byte) 0xFE)
+ return Float.POSITIVE_INFINITY;
+ if ((mValue[offset + 1] == 0x07 && mValue[offset] == (byte) 0xFF) ||
+ (mValue[offset + 1] == 0x08 && mValue[offset] == 0x00) ||
+ (mValue[offset + 1] == 0x08 && mValue[offset] == 0x01))
+ return Float.NaN;
+ if (mValue[offset + 1] == 0x08 && mValue[offset] == 0x02)
+ return Float.NEGATIVE_INFINITY;
+
+ return bytesToFloat(mValue[offset], mValue[offset + 1]);
+
+ case FORMAT_FLOAT:
+ if (mValue[offset + 3] == 0x00) {
+ if (mValue[offset + 2] == 0x7F && mValue[offset + 1] == (byte) 0xFF) {
+ if (mValue[offset] == (byte) 0xFE)
+ return Float.POSITIVE_INFINITY;
+ if (mValue[offset] == (byte) 0xFF)
+ return Float.NaN;
+ } else if (mValue[offset + 2] == (byte) 0x80 && mValue[offset + 1] == 0x00) {
+ if (mValue[offset] == 0x00 || mValue[offset] == 0x01)
+ return Float.NaN;
+ if (mValue[offset] == 0x02)
+ return Float.NEGATIVE_INFINITY;
+ }
+ }
+
+ return bytesToFloat(mValue[offset], mValue[offset + 1],
+ mValue[offset + 2], mValue[offset + 3]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the size of a give value type.
+ */
+ public static int getTypeLen(@ValueFormat final int formatType) {
+ return formatType & 0xF;
+ }
+
+ /**
+ * Convert a signed byte to an unsigned int.
+ */
+ private static int unsignedByteToInt(final byte b) {
+ return b & 0xFF;
+ }
+
+ /**
+ * Convert a signed byte to an unsigned int.
+ */
+ private static long unsignedByteToLong(final byte b) {
+ return b & 0xFFL;
+ }
+
+ /**
+ * Convert signed bytes to a 16-bit unsigned int.
+ */
+ private static int unsignedBytesToInt(final byte b0, final byte b1) {
+ return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit unsigned int.
+ */
+ private static int unsignedBytesToInt(final byte b0, final byte b1, final byte b2, final byte b3) {
+ return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
+ + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit unsigned long.
+ */
+ private static long unsignedBytesToLong(final byte b0, final byte b1, final byte b2, final byte b3) {
+ return (unsignedByteToLong(b0) + (unsignedByteToLong(b1) << 8))
+ + (unsignedByteToLong(b2) << 16) + (unsignedByteToLong(b3) << 24);
+ }
+
+ /**
+ * Convert signed bytes to a 16-bit short float value.
+ */
+ private static float bytesToFloat(final byte b0, final byte b1) {
+ int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ + ((unsignedByteToInt(b1) & 0x0F) << 8), 12);
+ int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4);
+ return (float) (mantissa * Math.pow(10, exponent));
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit short float value.
+ */
+ private static float bytesToFloat(final byte b0, final byte b1, final byte b2, final byte b3) {
+ int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ + (unsignedByteToInt(b1) << 8)
+ + (unsignedByteToInt(b2) << 16), 24);
+ return (float) (mantissa * Math.pow(10, b3));
+ }
+
+ /**
+ * Convert an unsigned integer value to a two's-complement encoded
+ * signed value.
+ */
+ private static int unsignedToSigned(int unsigned, final int size) {
+ if ((unsigned & (1 << size - 1)) != 0) {
+ unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
+ }
+ return unsigned;
+ }
+
+ /**
+ * Convert an unsigned long value to a two's-complement encoded
+ * signed value.
+ */
+ private static long unsignedToSigned(long unsigned, final int size) {
+ if ((unsigned & (1 << size - 1)) != 0) {
+ unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
+ }
+ return unsigned;
+ }
+
+ // Parcelable
+ protected Data(final Parcel in) {
+ mValue = in.createByteArray();
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeByteArray(mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Data createFromParcel(final Parcel in) {
+ return new Data(in);
+ }
+
+ @Override
+ public Data[] newArray(final int size) {
+ return new Data[size];
+ }
+ };
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/data/DataFilter.java b/app/src/main/java/no/nordicsemi/android/ble/data/DataFilter.java
new file mode 100644
index 0000000..321da2f
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/data/DataFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.data;
+
+import androidx.annotation.Nullable;
+
+public interface DataFilter {
+
+ /**
+ * This method should return true if the packet matches the filter and should be processed
+ * by the request.
+ *
+ * @param lastPacket the packet received.
+ * @return True, if packet should be processed, false if it should be ignored.
+ */
+ boolean filter(@Nullable final byte[] lastPacket);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/data/DataMerger.java b/app/src/main/java/no/nordicsemi/android/ble/data/DataMerger.java
new file mode 100644
index 0000000..2aa81f0
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/data/DataMerger.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.data;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public interface DataMerger {
+
+ /**
+ * This method should merge the last packet into the output message.
+ *
+ * @param output the stream for the output message, initially empty.
+ * @param lastPacket the data received in the last read/notify/indicate operation.
+ * @param index an index of the packet, 0-based (if you expect 3 packets, they will be
+ * called with indexes 0, 1, 2).
+ * @return True, if the message is complete, false if more data are expected.
+ */
+ boolean merge(@NonNull final DataStream output,
+ @Nullable final byte[] lastPacket, @IntRange(from = 0) final int index);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/data/DataSplitter.java b/app/src/main/java/no/nordicsemi/android/ble/data/DataSplitter.java
new file mode 100644
index 0000000..1cb1e9d
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/data/DataSplitter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.data;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public interface DataSplitter {
+
+ /**
+ * The implementation should return a index'th byte array from given message,
+ * with at most maxLength size, or null if no bytes are left to be sent.
+ *
+ * @param message the full message to be chunk.
+ * @param index index of a packet, 0-based.
+ * @param maxLength maximum length of the returned packet. Equals to MTU-3.
+ * Use {@link no.nordicsemi.android.ble.BleManager#requestMtu(int)} to request
+ * higher MTU, or {@link no.nordicsemi.android.ble.BleManager#overrideMtu(int)}
+ * If the MTU change was initiated by the target device.
+ * @return The packet to be sent, or null, if the whole message was already split.
+ */
+ @Nullable
+ byte[] chunk(@NonNull final byte[] message,
+ @IntRange(from = 0) final int index, @IntRange(from = 20) final int maxLength);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/data/DataStream.java b/app/src/main/java/no/nordicsemi/android/ble/data/DataStream.java
new file mode 100644
index 0000000..4aba77e
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/data/DataStream.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.data;
+
+import java.io.ByteArrayOutputStream;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+@SuppressWarnings("WeakerAccess")
+public class DataStream {
+ private final ByteArrayOutputStream buffer;
+
+ public DataStream() {
+ buffer = new ByteArrayOutputStream();
+ }
+
+ @SuppressWarnings("SimplifiableIfStatement")
+ public boolean write(@Nullable final byte[] data) {
+ if (data == null)
+ return false;
+
+ return write(data, 0, data.length);
+ }
+
+ public boolean write(@Nullable final byte[] data,
+ @IntRange(from = 0) final int offset, @IntRange(from = 0) final int length) {
+ if (data == null || data.length < offset)
+ return false;
+
+ final int len = Math.min(data.length - offset, length);
+ buffer.write(data, offset, len);
+ return true;
+ }
+
+ public boolean write(@Nullable final Data data) {
+ return data != null && write(data.getValue());
+ }
+
+ @IntRange(from = 0)
+ public int size() {
+ return buffer.size();
+ }
+
+ @NonNull
+ public byte[] toByteArray() {
+ return buffer.toByteArray();
+ }
+
+ @NonNull
+ public Data toData() {
+ return new Data(buffer.toByteArray());
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/data/DefaultMtuSplitter.java b/app/src/main/java/no/nordicsemi/android/ble/data/DefaultMtuSplitter.java
new file mode 100644
index 0000000..5c15484
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/data/DefaultMtuSplitter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.data;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Splits the message into at-most MTU-3 size packets.
+ */
+public final class DefaultMtuSplitter implements DataSplitter {
+
+ @Nullable
+ @Override
+ public byte[] chunk(@NonNull final byte[] message,
+ @IntRange(from = 0) final int index,
+ @IntRange(from = 20) final int maxLength) {
+ final int offset = index * maxLength;
+ final int length = Math.min(maxLength, message.length - offset);
+
+ if (length <= 0)
+ return null;
+
+ final byte[] data = new byte[length];
+ System.arraycopy(message, offset, data, 0, length);
+ return data;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/data/MutableData.java b/app/src/main/java/no/nordicsemi/android/ble/data/MutableData.java
new file mode 100644
index 0000000..acfe914
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/data/MutableData.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.data;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+@SuppressWarnings({"unused", "SameParameterValue", "WeakerAccess", "UnusedReturnValue"})
+public class MutableData extends Data {
+ // Values required to convert float to IEEE-11073 SFLOAT
+ private final static int SFLOAT_POSITIVE_INFINITY = 0x07FE;
+ private final static int SFLOAT_NAN = 0x07FF;
+ // private final static int SFLOAT_NOT_AT_THIS_RESOLUTION = 0x0800;
+ // private final static int SFLOAT_RESERVED_VALUE = 0x0801;
+ private final static int SFLOAT_NEGATIVE_INFINITY = 0x0802;
+ private final static int SFLOAT_MANTISSA_MAX = 0x07FD;
+ private final static int SFLOAT_EXPONENT_MAX = 7;
+ private final static int SFLOAT_EXPONENT_MIN = -8;
+ private final static float SFLOAT_MAX = 20450000000.0f;
+ private final static float SFLOAT_MIN = -SFLOAT_MAX;
+ private final static int SFLOAT_PRECISION = 10000;
+ // private final static float SFLOAT_EPSILON = 1e-8f;
+
+ // Values required to convert float to IEEE-11073 FLOAT
+ private final static int FLOAT_POSITIVE_INFINITY = 0x007FFFFE;
+ private final static int FLOAT_NAN = 0x007FFFFF;
+ // private final static int FLOAT_NOT_AT_THIS_RESOLUTION = 0x00800000;
+ // private final static int FLOAT_RESERVED_VALUE = 0x00800001;
+ private final static int FLOAT_NEGATIVE_INFINITY = 0x00800002;
+ private final static int FLOAT_MANTISSA_MAX = 0x007FFFFD;
+ private final static int FLOAT_EXPONENT_MAX = 127;
+ private final static int FLOAT_EXPONENT_MIN = -128;
+ private final static int FLOAT_PRECISION = 10000000;
+ // private final static float FLOAT_EPSILON = 1e-128f;
+
+ public MutableData() {
+ super();
+ }
+
+ public MutableData(@Nullable final byte[] data) {
+ super(data);
+ }
+
+ public static MutableData from(@NonNull final BluetoothGattCharacteristic characteristic) {
+ return new MutableData(characteristic.getValue());
+ }
+
+ public static MutableData from(@NonNull final BluetoothGattDescriptor descriptor) {
+ return new MutableData(descriptor.getValue());
+ }
+
+ /**
+ * Updates the locally stored value of this data.
+ *
+ * @param value New value
+ * @return true if the locally stored value has been set, false if the
+ * requested value could not be stored locally.
+ */
+ public boolean setValue(@Nullable final byte[] value) {
+ mValue = value;
+ return true;
+ }
+
+ /**
+ * Updates the byte at offset position.
+ *
+ * @param value Byte to set
+ * @param offset The offset
+ * @return true if the locally stored value has been set, false if the
+ * requested value could not be stored locally.
+ */
+ public boolean setByte(final int value, @IntRange(from = 0) final int offset) {
+ final int len = offset + 1;
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+ mValue[offset] = (byte) value;
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this data.
+ * See {@link #setValue(byte[])} for details.
+ *
+ * @param value New value for this data
+ * @param formatType Integer format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(int value, @IntFormat int formatType, @IntRange(from = 0) int offset) {
+ final int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SINT8:
+ value = intToSignedBits(value, 8);
+ // Fall-through intended
+ case FORMAT_UINT8:
+ mValue[offset] = (byte) (value & 0xFF);
+ break;
+
+ case FORMAT_SINT16:
+ value = intToSignedBits(value, 16);
+ // Fall-through intended
+ case FORMAT_UINT16:
+ mValue[offset++] = (byte) (value & 0xFF);
+ mValue[offset] = (byte) ((value >> 8) & 0xFF);
+ break;
+
+ case FORMAT_SINT24:
+ value = intToSignedBits(value, 24);
+ // Fall-through intended
+ case FORMAT_UINT24:
+ mValue[offset++] = (byte) (value & 0xFF);
+ mValue[offset++] = (byte) ((value >> 8) & 0xFF);
+ mValue[offset] = (byte) ((value >> 16) & 0xFF);
+ break;
+
+ case FORMAT_SINT32:
+ value = intToSignedBits(value, 32);
+ // Fall-through intended
+ case FORMAT_UINT32:
+ mValue[offset++] = (byte) (value & 0xFF);
+ mValue[offset++] = (byte) ((value >> 8) & 0xFF);
+ mValue[offset++] = (byte) ((value >> 16) & 0xFF);
+ mValue[offset] = (byte) ((value >> 24) & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this data.
+ * See {@link #setValue(byte[])} for details.
+ *
+ * @param mantissa Mantissa for this data
+ * @param exponent Exponent value for this data
+ * @param formatType Float format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(int mantissa, int exponent,
+ @FloatFormat int formatType, @IntRange(from = 0) int offset) {
+ final int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ mantissa = intToSignedBits(mantissa, 12);
+ exponent = intToSignedBits(exponent, 4);
+ mValue[offset++] = (byte) (mantissa & 0xFF);
+ mValue[offset] = (byte) ((mantissa >> 8) & 0x0F);
+ mValue[offset] += (byte) ((exponent & 0x0F) << 4);
+ break;
+
+ case FORMAT_FLOAT:
+ mantissa = intToSignedBits(mantissa, 24);
+ exponent = intToSignedBits(exponent, 8);
+ mValue[offset++] = (byte) (mantissa & 0xFF);
+ mValue[offset++] = (byte) ((mantissa >> 8) & 0xFF);
+ mValue[offset++] = (byte) ((mantissa >> 16) & 0xFF);
+ mValue[offset] += (byte) (exponent & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this data.
+ * See {@link #setValue(byte[])} for details.
+ *
+ * @param value New value for this data. This allows to send {@link #FORMAT_UINT32}.
+ * @param formatType Integer format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(long value, @LongFormat int formatType, @IntRange(from = 0) int offset) {
+ final int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SINT32:
+ value = longToSignedBits(value, 32);
+ // Fall-through intended
+ case FORMAT_UINT32:
+ mValue[offset++] = (byte) (value & 0xFF);
+ mValue[offset++] = (byte) ((value >> 8) & 0xFF);
+ mValue[offset++] = (byte) ((value >> 16) & 0xFF);
+ mValue[offset] = (byte) ((value >> 24) & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this data.
+ * See {@link #setValue(byte[])} for details.
+ *
+ * @param value Float value to be written
+ * @param formatType Float format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(float value,
+ @FloatFormat int formatType, @IntRange(from = 0) int offset) {
+ final int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ final int sfloatAsInt = sfloatToInt(value);
+ mValue[offset++] = (byte) (sfloatAsInt & 0xFF);
+ mValue[offset] = (byte) ((sfloatAsInt >> 8) & 0xFF);
+ break;
+
+ case FORMAT_FLOAT:
+ final int floatAsInt = floatToInt(value);
+ mValue[offset++] = (byte) (floatAsInt & 0xFF);
+ mValue[offset++] = (byte) ((floatAsInt >> 8) & 0xFF);
+ mValue[offset++] = (byte) ((floatAsInt >> 16) & 0xFF);
+ mValue[offset] += (byte) ((floatAsInt >> 24) & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Converts float to SFLOAT IEEE 11073 format as UINT16, rounding up or down.
+ * See: https://github.com/signove/antidote/blob/master/src/util/bytelib.c
+ *
+ * @param value the value to be converted.
+ * @return given float as UINT16 in IEEE 11073 format.
+ */
+ private static int sfloatToInt(final float value) {
+ if (Float.isNaN(value)) {
+ return SFLOAT_NAN;
+ } else if (value > SFLOAT_MAX) {
+ return SFLOAT_POSITIVE_INFINITY;
+ } else if (value < SFLOAT_MIN) {
+ return SFLOAT_NEGATIVE_INFINITY;
+ }
+
+ int sign = value >= 0 ? +1 : -1;
+ float mantissa = Math.abs(value);
+ int exponent = 0; // Note: 10**x exponent, not 2**x
+
+ // scale up if number is too big
+ while (mantissa > SFLOAT_MANTISSA_MAX) {
+ mantissa /= 10.0f;
+ ++exponent;
+ if (exponent > SFLOAT_EXPONENT_MAX) {
+ // argh, should not happen
+ if (sign > 0) {
+ return SFLOAT_POSITIVE_INFINITY;
+ } else {
+ return SFLOAT_NEGATIVE_INFINITY;
+ }
+ }
+ }
+
+ // scale down if number is too small
+ while (mantissa < 1) {
+ mantissa *= 10;
+ --exponent;
+ if (exponent < SFLOAT_EXPONENT_MIN) {
+ // argh, should not happen
+ return 0;
+ }
+ }
+
+ // scale down if number needs more precision
+ double smantissa = Math.round(mantissa * SFLOAT_PRECISION);
+ double rmantissa = Math.round(mantissa) * SFLOAT_PRECISION;
+ double mdiff = Math.abs(smantissa - rmantissa);
+ while (mdiff > 0.5 && exponent > SFLOAT_EXPONENT_MIN &&
+ (mantissa * 10) <= SFLOAT_MANTISSA_MAX) {
+ mantissa *= 10;
+ --exponent;
+ smantissa = Math.round(mantissa * SFLOAT_PRECISION);
+ rmantissa = Math.round(mantissa) * SFLOAT_PRECISION;
+ mdiff = Math.abs(smantissa - rmantissa);
+ }
+
+ int int_mantissa = Math.round(sign * mantissa);
+ return ((exponent & 0xF) << 12) | (int_mantissa & 0xFFF);
+ }
+
+ /**
+ * Converts float to FLOAT IEEE 11073 format as UINT32, rounding up or down.
+ * See: https://github.com/signove/antidote/blob/master/src/util/bytelib.c
+ *
+ * @param value the value to be converted.
+ * @return given float as UINT32 in IEEE 11073 format.
+ */
+ private static int floatToInt(final float value) {
+ if (Float.isNaN(value)) {
+ return FLOAT_NAN;
+ } else if (value == Float.POSITIVE_INFINITY) {
+ return FLOAT_POSITIVE_INFINITY;
+ } else if (value == Float.NEGATIVE_INFINITY) {
+ return FLOAT_NEGATIVE_INFINITY;
+ }
+
+ int sign = value >= 0 ? +1 : -1;
+ float mantissa = Math.abs(value);
+ int exponent = 0; // Note: 10**x exponent, not 2**x
+
+ // scale up if number is too big
+ while (mantissa > FLOAT_MANTISSA_MAX) {
+ mantissa /= 10.0f;
+ ++exponent;
+ if (exponent > FLOAT_EXPONENT_MAX) {
+ // argh, should not happen
+ if (sign > 0) {
+ return FLOAT_POSITIVE_INFINITY;
+ } else {
+ return FLOAT_NEGATIVE_INFINITY;
+ }
+ }
+ }
+
+ // scale down if number is too small
+ while (mantissa < 1) {
+ mantissa *= 10;
+ --exponent;
+ if (exponent < FLOAT_EXPONENT_MIN) {
+ // argh, should not happen
+ return 0;
+ }
+ }
+
+ // scale down if number needs more precision
+ double smantissa = Math.round(mantissa * FLOAT_PRECISION);
+ double rmantissa = Math.round(mantissa) * FLOAT_PRECISION;
+ double mdiff = Math.abs(smantissa - rmantissa);
+ while (mdiff > 0.5 && exponent > FLOAT_EXPONENT_MIN &&
+ (mantissa * 10) <= FLOAT_MANTISSA_MAX) {
+ mantissa *= 10;
+ --exponent;
+ smantissa = Math.round(mantissa * FLOAT_PRECISION);
+ rmantissa = Math.round(mantissa) * FLOAT_PRECISION;
+ mdiff = Math.abs(smantissa - rmantissa);
+ }
+
+ int int_mantissa = Math.round(sign * mantissa);
+ return (exponent << 24) | (int_mantissa & 0xFFFFFF);
+ }
+
+ /**
+ * Convert an integer into the signed bits of a given length.
+ */
+ private static int intToSignedBits(int i, int size) {
+ if (i < 0) {
+ i = (1 << size - 1) + (i & ((1 << size - 1) - 1));
+ }
+ return i;
+ }
+
+ /**
+ * Convert a long into the signed bits of a given length.
+ */
+ private static long longToSignedBits(long i, int size) {
+ if (i < 0) {
+ i = (1L << size - 1) + (i & ((1L << size - 1) - 1));
+ }
+ return i;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/error/GattError.java b/app/src/main/java/no/nordicsemi/android/ble/error/GattError.java
new file mode 100644
index 0000000..39dc53c
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/error/GattError.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package no.nordicsemi.android.ble.error;
+
+import android.bluetooth.BluetoothGatt;
+
+/**
+ * Parses the GATT and HCI errors to human readable strings.
+ *
+ * See: gatt_api.h for details.autoConnect flag set to false
+ * (to make the first connection quick) but on connection lost the manager will call
+ * {@link #connect(BluetoothDevice)} again. This time this method will call
+ * {@link BluetoothGatt#connect()} which always uses autoConnect equal true.
+ */
+ private boolean mInitialConnection;
+ /**
+ * Flag set to true when the device is connected.
+ */
+ private boolean mConnected;
+ /**
+ * A timestamp when the last connection attempt was made. This is distinguish two situations
+ * when the 133 error happens during a connection attempt: a timeout (when ~30 sec passed since
+ * connection was requested), or an error (packet collision, packet missed, etc.)
+ */
+ private long mConnectionTime;
+ /**
+ * A time after which receiving 133 error is considered a timeout, instead of a
+ * different reason.
+ * A {@link BluetoothDevice#connectGatt(Context, boolean, BluetoothGattCallback)} call will
+ * fail after 30 seconds if the device won't be found until then. Other errors happen much
+ * earlier. 20 sec should be OK here.
+ */
+ private final static long CONNECTION_TIMEOUT_THRESHOLD = 20000; // ms
+ /**
+ * Flag set to true when the initialization queue is complete.
+ */
+ private boolean mReady;
+ /**
+ * Flag set when services were discovered.
+ */
+ private boolean mServicesDiscovered;
+ private boolean mServiceDiscoveryRequested;
+ private int mConnectionCount = 0;
+ /**
+ * Connection state. One of {@link BluetoothGatt#STATE_CONNECTED},
+ * {@link BluetoothGatt#STATE_CONNECTING}, {@link BluetoothGatt#STATE_DISCONNECTED},
+ * {@link BluetoothGatt#STATE_DISCONNECTING}.
+ */
+ private int mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
+ /**
+ * Last received battery value or -1 if value wasn't received.
+ *
+ * @deprecated Battery value should be kept in the profile manager instead. See BatteryManager
+ * class in Android nRF Toolbox app.
+ */
+ @IntRange(from = -1, to = 100)
+ @Deprecated
+ private int mBatteryValue = -1;
+ /**
+ * The current MTU (Maximum Transfer Unit). The maximum number of bytes that can be sent in
+ * a single packet is MTU-3.
+ */
+ private int mMtu = 23;
+ /** A flag indicating that Reliable Write is in progress. */
+ private boolean mReliableWriteInProgress;
+ /**
+ * The connect request. This is instantiated in {@link #connect(BluetoothDevice, int)}
+ * and nullified after the device is ready.
+ * false.
+ *
+ * @return true when the request has been sent, false 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);
+ }
+
+ /**
+ * Returns the callback that is registered for value changes (notifications) of given
+ * characteristic. After assigning the notifications callback, notifications must be
+ * enabled using {@link #enableNotifications(BluetoothGattCharacteristic)}.
+ * This applies also when they were already enabled on the remote side.
+ *
+ * beginReliableWrite()
+ * .add(writeCharacteristic(someCharacteristic, someData)
+ * .fail(...)
+ * .done(...))
+ * // Non-write requests are also possible
+ * .add(requestMtu(200))
+ * // Data will be written in the same order
+ * .add(writeCharacteristic(someCharacteristic, differentData))
+ * // This will return the OLD data, not 'differentData', as the RW wasn't executed!
+ * .add(readCharacteristic(someCharacteristic).with(callback))
+ * // Multiple characteristics may be written during a single RW
+ * .add(writeCharacteristic(someOtherCharacteristic, importantData))
+ * // Finally, enqueue the Reliable Write request in BleManager
+ * .enqueue();
+ *
+ *
+ * @return The request.
+ */
+ @NonNull
+ protected ReliableWriteRequest beginReliableWrite() {
+ return Request.newReliableWriteRequest().setManager(this);
+ }
+
+ @MainThread
+ private boolean internalBeginReliableWrite() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ // Reliable Write can't be before the old one isn't executed or aborted.
+ if (mReliableWriteInProgress)
+ return true;
+
+ log(Log.VERBOSE, "Beginning reliable write...");
+ log(Log.DEBUG, "gatt.beginReliableWrite()");
+ return mReliableWriteInProgress = gatt.beginReliableWrite();
+ }
+
+ @MainThread
+ private boolean internalExecuteReliableWrite() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ if (!mReliableWriteInProgress)
+ return false;
+
+ log(Log.VERBOSE, "Executing reliable write...");
+ log(Log.DEBUG, "gatt.executeReliableWrite()");
+ return gatt.executeReliableWrite();
+ }
+
+ @MainThread
+ private boolean internalAbortReliableWrite() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ if (!mReliableWriteInProgress)
+ return false;
+
+ log(Log.VERBOSE, "Aborting reliable write...");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ log(Log.DEBUG, "gatt.abortReliableWrite()");
+ gatt.abortReliableWrite();
+ } else {
+ log(Log.DEBUG, "gatt.abortReliableWrite(device)");
+ gatt.abortReliableWrite(mBluetoothDevice);
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if {@link BluetoothGatt#beginReliableWrite()} has been called and
+ * the Reliable Write hasn't been executed nor aborted yet.
+ */
+ protected final boolean isReliableWriteInProgress() {
+ return mReliableWriteInProgress;
+ }
+
+ /**
+ * Reads the battery level from the device.
+ *
+ * @deprecated Use {@link #readCharacteristic(BluetoothGattCharacteristic)} instead.
+ */
+ @SuppressWarnings("ConstantConditions")
+ @Deprecated
+ protected void readBatteryLevel() {
+ Request.newReadBatteryLevelRequest().setManager(this)
+ .with((device, data) -> {
+ if (data.size() == 1) {
+ final int batteryLevel = data.getIntValue(Data.FORMAT_UINT8, 0);
+ log(Log.INFO, "Battery Level received: " + batteryLevel + "%");
+ mBatteryValue = batteryLevel;
+ final BleManagerGattCallback callback = mGattCallback;
+ if (callback != null) {
+ callback.onBatteryValueReceived(mBluetoothGatt, batteryLevel);
+ }
+ mCallbacks.onBatteryValueReceived(device, batteryLevel);
+ }
+ })
+ .enqueue();
+ }
+
+ @MainThread
+ @Deprecated
+ private boolean internalReadBatteryLevel() {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE);
+ if (batteryService == null)
+ return false;
+
+ final BluetoothGattCharacteristic batteryLevelCharacteristic =
+ batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
+ return internalReadCharacteristic(batteryLevelCharacteristic);
+ }
+
+ /**
+ * This method enables notifications on the Battery Level characteristic.
+ *
+ * @deprecated Use {@link #setNotificationCallback(BluetoothGattCharacteristic)} and
+ * {@link #enableNotifications(BluetoothGattCharacteristic)} instead.
+ */
+ @SuppressWarnings("ConstantConditions")
+ @Deprecated
+ protected void enableBatteryLevelNotifications() {
+ if (mBatteryLevelNotificationCallback == null) {
+ mBatteryLevelNotificationCallback = new ValueChangedCallback()
+ .with((device, data) -> {
+ if (data.size() == 1) {
+ final int batteryLevel = data.getIntValue(Data.FORMAT_UINT8, 0);
+ mBatteryValue = batteryLevel;
+ final BleManagerGattCallback callback = mGattCallback;
+ if (callback != null) {
+ callback.onBatteryValueReceived(mBluetoothGatt, batteryLevel);
+ }
+ mCallbacks.onBatteryValueReceived(device, batteryLevel);
+ }
+ });
+ }
+ Request.newEnableBatteryLevelNotificationsRequest().setManager(this)
+ .done(device -> log(Log.INFO, "Battery Level notifications enabled"))
+ .enqueue();
+ }
+
+ /**
+ * This method disables notifications on the Battery Level characteristic.
+ *
+ * @deprecated Use {@link #disableNotifications(BluetoothGattCharacteristic)} instead.
+ */
+ @Deprecated
+ protected void disableBatteryLevelNotifications() {
+ Request.newDisableBatteryLevelNotificationsRequest().setManager(this)
+ .done(device -> log(Log.INFO, "Battery Level notifications disabled"))
+ .enqueue();
+ }
+
+ @MainThread
+ @Deprecated
+ private boolean internalSetBatteryNotifications(final boolean enable) {
+ final BluetoothGatt gatt = mBluetoothGatt;
+ if (gatt == null || !mConnected)
+ return false;
+
+ final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE);
+ if (batteryService == null)
+ return false;
+
+ final BluetoothGattCharacteristic batteryLevelCharacteristic = batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
+ if (enable)
+ return internalEnableNotifications(batteryLevelCharacteristic);
+ else
+ return internalDisableNotifications(batteryLevelCharacteristic);
+ }
+
+ /**
+ * There was a bug in Android up to 6.0 where the descriptor was written using parent
+ * characteristic's write type, instead of always Write With Response, as the spec says.
+ *
+ *
+ * Works only on Android Lollipop or newer. On older system versions will cause
+ * {@link Request#fail(FailCallback)} callback or throw
+ * {@link no.nordicsemi.android.ble.exception.RequestFailedException} with
+ * {@link FailCallback#REASON_REQUEST_FAILED} status if called synchronously.
+ * Starting from Android Oreo you may get a callback with the interval, latency and timeout
+ * using {@link ConnectionPriorityRequest#with(ConnectionPriorityCallback)}.
+ * true when the gatt device supports the
+ * required services.
+ *
+ * @param gatt the gatt device with services discovered
+ * @return True when the device has the required service.
+ */
+ protected abstract boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt);
+
+ /**
+ * This method should return true when the gatt device supports the
+ * optional services. The default implementation returns false.
+ *
+ * @param gatt the gatt device with services discovered
+ * @return True when the device has the optional service.
+ */
+ protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) {
+ return false;
+ }
+
+ /**
+ * This method should return a list of requests needed to initialize the profile.
+ * Enabling Service Change indications for bonded devices and reading the Battery Level
+ * value and enabling Battery Level notifications is handled before executing this queue.
+ * The queue should not have requests that are not available, e.g. should not read an
+ * optional service when it is not supported by the connected device.
+ *
+ * @Override
+ * protected void initialize() {
+ * requestMtu(MTU)
+ * .with((device, mtu) -> {
+ * ...
+ * })
+ * .enqueue();
+ * setNotificationCallback(characteristic)
+ * .with((device, data) -> {
+ * ...
+ * });
+ * enableNotifications(characteristic)
+ * .done(device -> {
+ * ...
+ * })
+ * .fail((device, status) -> {
+ * ...
+ * })
+ * .enqueue();
+ * }
+ *
+ */
+ protected void initialize() {
+ // empty initialization queue
+ }
+
+ /**
+ * Called then the initialization queue is complete.
+ */
+ protected void onDeviceReady() {
+ mCallbacks.onDeviceReady(mBluetoothGatt.getDevice());
+ }
+
+ /**
+ * Called each time the task queue gets cleared.
+ */
+ protected void onManagerReady() {
+ // empty
+ }
+
+ /**
+ * This method should nullify all services and characteristics of the device.
+ * It's called when the device is no longer connected, either due to user action
+ * or a link loss.
+ */
+ protected abstract void onDeviceDisconnected();
+
+ private void notifyDeviceDisconnected(@NonNull final BluetoothDevice device) {
+ final boolean wasConnected = mConnected;
+ mConnected = false;
+ mServicesDiscovered = false;
+ mServiceDiscoveryRequested = false;
+ mInitInProgress = false;
+ mConnectionState = BluetoothGatt.STATE_DISCONNECTED;
+ if (!wasConnected) {
+ log(Log.WARN, "Connection attempt timed out");
+ close();
+ mCallbacks.onDeviceDisconnected(device);
+ // ConnectRequest was already notified
+ } else if (mUserDisconnected) {
+ log(Log.INFO, "Disconnected");
+ close();
+ mCallbacks.onDeviceDisconnected(device);
+ final Request request = mRequest;
+ if (request != null && request.type == Request.Type.DISCONNECT) {
+ request.notifySuccess(device);
+ }
+ } else {
+ log(Log.WARN, "Connection lost");
+ mCallbacks.onLinkLossOccurred(device);
+ // 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.
+ }
+ onDeviceDisconnected();
+ }
+
+ /**
+ * Callback reporting the result of a characteristic read operation.
+ *
+ * @param gatt GATT client
+ * @param characteristic Characteristic that was read from the associated remote device.
+ * @deprecated Use {@link ReadRequest#with(DataReceivedCallback)} instead.
+ */
+ @Deprecated
+ protected void onCharacteristicRead(@NonNull final BluetoothGatt gatt,
+ @NonNull final BluetoothGattCharacteristic characteristic) {
+ // do nothing
+ }
+
+ /**
+ * Callback indicating the result of a characteristic write operation.
+ * true the secondary services were also found
+ * on the device.
+ */
+ void onServicesDiscovered(@NonNull final BluetoothDevice device, final boolean optionalServicesFound);
+
+ /**
+ * Method called when all initialization requests has been completed.
+ *
+ * @param device the device that get ready.
+ */
+ void onDeviceReady(@NonNull final BluetoothDevice device);
+
+ /**
+ * This method should return true if Battery Level notifications should be enabled on the
+ * target device. If there is no Battery Service, or the Battery Level characteristic does
+ * not have NOTIFY property, this method will not be called for this device.
+ * {@code
+ * setNotificationCallback(batteryLevelCharacteristic)
+ * .with(new BatteryLevelDataCallback() {
+ * onBatteryLevelChanged(int batteryLevel) {
+ * ...
+ * }
+ * });
+ * }
+ * in the {@link BleManager.BleManagerGattCallback#initialize() initialize(BluetoothDevice)}
+ * instead.
+ */
+ @Deprecated
+ default boolean shouldEnableBatteryLevelNotifications(@NonNull final BluetoothDevice device) {
+ return false;
+ }
+
+ /**
+ * Called when battery value has been received from the device.
+ *
+ * @param device the device from which the battery value has changed.
+ * @param value the battery value in percent.
+ * @deprecated Use
+ * {@code
+ * setNotificationCallback(batteryLevelCharacteristic)
+ * .with(new BatteryLevelDataCallback() {
+ * onBatteryLevelChanged(int batteryLevel) {
+ * ...
+ * }
+ * });
+ * }
+ * in the {@link BleManager.BleManagerGattCallback#initialize() initialize(BluetoothDevice)}
+ * instead.
+ */
+ @Deprecated
+ default void onBatteryValueReceived(@NonNull final BluetoothDevice device,
+ @IntRange(from = 0, to = 100) final int value) {
+ // do nothing
+ }
+
+ /**
+ * Called when an {@link BluetoothGatt#GATT_INSUFFICIENT_AUTHENTICATION} error occurred and the
+ * device bond state is {@link BluetoothDevice#BOND_NONE}.
+ *
+ * @param device the device that requires bonding.
+ */
+ void onBondingRequired(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when the device has been successfully bonded.
+ *
+ * @param device the device that got bonded.
+ */
+ void onBonded(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when the bond state has changed from {@link BluetoothDevice#BOND_BONDING} to
+ * {@link BluetoothDevice#BOND_NONE}.
+ *
+ * @param device the device that failed to bond.
+ */
+ void onBondingFailed(@NonNull final BluetoothDevice device);
+
+ /**
+ * Called when a BLE error has occurred
+ *
+ * @param message the error message.
+ * @param errorCode the error code.
+ * @param device the device that caused an error.
+ */
+ void onError(@NonNull final BluetoothDevice device,
+ @NonNull final String message, final int errorCode);
+
+ /**
+ * Called when service discovery has finished but the main services were not found on the device.
+ *
+ * @param device the device that failed to connect due to lack of required services.
+ */
+ void onDeviceNotSupported(@NonNull final BluetoothDevice device);
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java b/app/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java
new file mode 100644
index 0000000..567acae
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.content.Context;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.annotation.PhyMask;
+import no.nordicsemi.android.ble.callback.BeforeCallback;
+import no.nordicsemi.android.ble.callback.FailCallback;
+import no.nordicsemi.android.ble.callback.InvalidRequestCallback;
+import no.nordicsemi.android.ble.callback.SuccessCallback;
+
+/**
+ * The connect request is used to connect to a Bluetooth LE device. The request will end when
+ * the device gets connected, the connection timeouts, or an error occurs.
+ *
+ *
+ * Requesting connection priority is available on Android Lollipop or newer. On older
+ * platforms the request will enqueue, but will fail to execute and {@link #fail(FailCallback)}
+ * callback will be called.
+ *
+ * @param priority one of: {@link ConnectionPriorityRequest#CONNECTION_PRIORITY_HIGH},
+ * {@link ConnectionPriorityRequest#CONNECTION_PRIORITY_BALANCED},
+ * {@link ConnectionPriorityRequest#CONNECTION_PRIORITY_LOW_POWER}.
+ * @return The new request.
+ * @deprecated Access to this method will change to package-only.
+ * Use {@link BleManager#requestConnectionPriority(int)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public static ConnectionPriorityRequest newConnectionPriorityRequest(
+ @ConnectionPriority final int priority) {
+ return new ConnectionPriorityRequest(Type.REQUEST_CONNECTION_PRIORITY, priority);
+ }
+
+ /**
+ * Requests the change of preferred PHY for this connections.
+ * await(...) method;
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ public SimpleValueRequestawait(...) method;
+ *
+ * @param callback the callback.
+ * @return The request.
+ */
+ @NonNull
+ public TimeoutableValueRequest
+ * See also: hcidefs.h for other possible HCI errors.
+ */
+@SuppressWarnings("WeakerAccess")
+public class GattError {
+ public static final int GATT_SUCCESS = BluetoothGatt.GATT_SUCCESS;
+ public static final int GATT_CONN_L2C_FAILURE = 0x01;
+ public static final int GATT_CONN_TIMEOUT = 0x08;
+ public static final int GATT_CONN_TERMINATE_PEER_USER = 0x13;
+ public static final int GATT_CONN_TERMINATE_LOCAL_HOST = 0x16;
+ public static final int GATT_CONN_FAIL_ESTABLISH = 0x3E;
+ public static final int GATT_CONN_LMP_TIMEOUT = 0x22;
+ public static final int GATT_CONN_CANCEL = 0x0100;
+ public static final int GATT_ERROR = 0x0085; // Device not reachable
+
+ public static final int GATT_INVALID_HANDLE = 0x0001;
+ public static final int GATT_READ_NOT_PERMIT = 0x0002;
+ public static final int GATT_WRITE_NOT_PERMIT = 0x0003;
+ public static final int GATT_INVALID_PDU = 0x0004;
+ public static final int GATT_INSUF_AUTHENTICATION = 0x0005;
+ public static final int GATT_REQ_NOT_SUPPORTED = 0x0006;
+ public static final int GATT_INVALID_OFFSET = 0x0007;
+ public static final int GATT_INSUF_AUTHORIZATION = 0x0008;
+ public static final int GATT_PREPARE_Q_FULL = 0x0009;
+ public static final int GATT_NOT_FOUND = 0x000a;
+ public static final int GATT_NOT_LONG = 0x000b;
+ public static final int GATT_INSUF_KEY_SIZE = 0x000c;
+ public static final int GATT_INVALID_ATTR_LEN = 0x000d;
+ public static final int GATT_ERR_UNLIKELY = 0x000e;
+ public static final int GATT_INSUF_ENCRYPTION = 0x000f;
+ public static final int GATT_UNSUPPORT_GRP_TYPE = 0x0010;
+ public static final int GATT_INSUF_RESOURCE = 0x0011;
+ public static final int GATT_CONTROLLER_BUSY = 0x003A;
+ public static final int GATT_UNACCEPT_CONN_INTERVAL = 0x003B;
+ public static final int GATT_ILLEGAL_PARAMETER = 0x0087;
+ public static final int GATT_NO_RESOURCES = 0x0080;
+ public static final int GATT_INTERNAL_ERROR = 0x0081;
+ public static final int GATT_WRONG_STATE = 0x0082;
+ public static final int GATT_DB_FULL = 0x0083;
+ public static final int GATT_BUSY = 0x0084;
+ public static final int GATT_CMD_STARTED = 0x0086;
+ public static final int GATT_PENDING = 0x0088;
+ public static final int GATT_AUTH_FAIL = 0x0089;
+ public static final int GATT_MORE = 0x008a;
+ public static final int GATT_INVALID_CFG = 0x008b;
+ public static final int GATT_SERVICE_STARTED = 0x008c;
+ public static final int GATT_ENCRYPTED_NO_MITM = 0x008d;
+ public static final int GATT_NOT_ENCRYPTED = 0x008e;
+ public static final int GATT_CONGESTED = 0x008f;
+ public static final int GATT_CCCD_CFG_ERROR = 0x00FD;
+ public static final int GATT_PROCEDURE_IN_PROGRESS = 0x00FE;
+ public static final int GATT_VALUE_OUT_OF_RANGE = 0x00FF;
+ public static final int TOO_MANY_OPEN_CONNECTIONS = 0x0101;
+
+ /**
+ * Converts the connection status given by the
+ * {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)}
+ * to error name.
+ *
+ * @param error the status number.
+ * @return The error name as stated in the links in {@link GattError} documentation.
+ */
+ public static String parseConnectionError(final int error) {
+ switch (error) {
+ case GATT_SUCCESS:
+ return "SUCCESS";
+ case GATT_CONN_L2C_FAILURE:
+ return "GATT CONN L2C FAILURE";
+ case GATT_CONN_TIMEOUT:
+ return "GATT CONN TIMEOUT";
+ case GATT_CONN_TERMINATE_PEER_USER:
+ return "GATT CONN TERMINATE PEER USER";
+ case GATT_CONN_TERMINATE_LOCAL_HOST:
+ return "GATT CONN TERMINATE LOCAL HOST";
+ case GATT_CONN_FAIL_ESTABLISH:
+ return "GATT CONN FAIL ESTABLISH";
+ case GATT_CONN_LMP_TIMEOUT:
+ return "GATT CONN LMP TIMEOUT";
+ case GATT_CONN_CANCEL:
+ return "GATT CONN CANCEL ";
+ case GATT_ERROR:
+ return "GATT ERROR"; // Device not reachable
+ default:
+ return "UNKNOWN (" + error + ")";
+ }
+ }
+
+ /**
+ * Converts the Bluetooth communication status given by other BluetoothGattCallbacks to error
+ * name. It also parses the DFU errors.
+ *
+ * @param error the status number.
+ * @return The error name as stated in the links in {@link GattError} documentation.
+ */
+ public static String parse(final int error) {
+ switch (error) {
+ case GATT_INVALID_HANDLE:
+ return "GATT INVALID HANDLE";
+ case GATT_READ_NOT_PERMIT:
+ return "GATT READ NOT PERMIT";
+ case GATT_WRITE_NOT_PERMIT:
+ return "GATT WRITE NOT PERMIT";
+ case GATT_INVALID_PDU:
+ return "GATT INVALID PDU";
+ case GATT_INSUF_AUTHENTICATION:
+ return "GATT INSUF AUTHENTICATION";
+ case GATT_REQ_NOT_SUPPORTED:
+ return "GATT REQ NOT SUPPORTED";
+ case GATT_INVALID_OFFSET:
+ return "GATT INVALID OFFSET";
+ case GATT_INSUF_AUTHORIZATION:
+ return "GATT INSUF AUTHORIZATION";
+ case GATT_PREPARE_Q_FULL:
+ return "GATT PREPARE Q FULL";
+ case GATT_NOT_FOUND:
+ return "GATT NOT FOUND";
+ case GATT_NOT_LONG:
+ return "GATT NOT LONG";
+ case GATT_INSUF_KEY_SIZE:
+ return "GATT INSUF KEY SIZE";
+ case GATT_INVALID_ATTR_LEN:
+ return "GATT INVALID ATTR LEN";
+ case GATT_ERR_UNLIKELY:
+ return "GATT ERR UNLIKELY";
+ case GATT_INSUF_ENCRYPTION:
+ return "GATT INSUF ENCRYPTION";
+ case GATT_UNSUPPORT_GRP_TYPE:
+ return "GATT UNSUPPORT GRP TYPE";
+ case GATT_INSUF_RESOURCE:
+ return "GATT INSUF RESOURCE";
+ case GATT_CONN_LMP_TIMEOUT:
+ return "GATT CONN LMP TIMEOUT";
+ case GATT_CONTROLLER_BUSY:
+ return "GATT CONTROLLER BUSY";
+ case GATT_UNACCEPT_CONN_INTERVAL:
+ return "GATT UNACCEPT CONN INTERVAL";
+ case GATT_ILLEGAL_PARAMETER:
+ return "GATT ILLEGAL PARAMETER";
+ case GATT_NO_RESOURCES:
+ return "GATT NO RESOURCES";
+ case GATT_INTERNAL_ERROR:
+ return "GATT INTERNAL ERROR";
+ case GATT_WRONG_STATE:
+ return "GATT WRONG STATE";
+ case GATT_DB_FULL:
+ return "GATT DB FULL";
+ case GATT_BUSY:
+ return "GATT BUSY";
+ case GATT_ERROR:
+ return "GATT ERROR";
+ case GATT_CMD_STARTED:
+ return "GATT CMD STARTED";
+ case GATT_PENDING:
+ return "GATT PENDING";
+ case GATT_AUTH_FAIL:
+ return "GATT AUTH FAIL";
+ case GATT_MORE:
+ return "GATT MORE";
+ case GATT_INVALID_CFG:
+ return "GATT INVALID CFG";
+ case GATT_SERVICE_STARTED:
+ return "GATT SERVICE STARTED";
+ case GATT_ENCRYPTED_NO_MITM:
+ return "GATT ENCRYPTED NO MITM";
+ case GATT_NOT_ENCRYPTED:
+ return "GATT NOT ENCRYPTED";
+ case GATT_CONGESTED:
+ return "GATT CONGESTED";
+ case GATT_CCCD_CFG_ERROR:
+ return "GATT CCCD CFG ERROR";
+ case GATT_PROCEDURE_IN_PROGRESS:
+ return "GATT PROCEDURE IN PROGRESS";
+ case GATT_VALUE_OUT_OF_RANGE:
+ return "GATT VALUE OUT OF RANGE";
+ case TOO_MANY_OPEN_CONNECTIONS:
+ return "TOO MANY OPEN CONNECTIONS";
+ default:
+ return "UNKNOWN (" + error + ")";
+ }
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/exception/BluetoothDisabledException.java b/app/src/main/java/no/nordicsemi/android/ble/exception/BluetoothDisabledException.java
new file mode 100644
index 0000000..7c07421
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/exception/BluetoothDisabledException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.exception;
+
+public class BluetoothDisabledException extends ConnectionException {
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/exception/ConnectionException.java b/app/src/main/java/no/nordicsemi/android/ble/exception/ConnectionException.java
new file mode 100644
index 0000000..40794d7
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/exception/ConnectionException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.exception;
+
+@SuppressWarnings("WeakerAccess")
+public class ConnectionException extends Exception {
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/exception/DeviceDisconnectedException.java b/app/src/main/java/no/nordicsemi/android/ble/exception/DeviceDisconnectedException.java
new file mode 100644
index 0000000..ccca470
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/exception/DeviceDisconnectedException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.exception;
+
+public class DeviceDisconnectedException extends ConnectionException {
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/exception/InvalidDataException.java b/app/src/main/java/no/nordicsemi/android/ble/exception/InvalidDataException.java
new file mode 100644
index 0000000..3ae6a05
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/exception/InvalidDataException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.exception;
+
+import androidx.annotation.NonNull;
+import no.nordicsemi.android.ble.callback.profile.ProfileReadResponse;
+
+@SuppressWarnings("unused")
+public final class InvalidDataException extends Exception {
+ private final ProfileReadResponse response;
+
+ public InvalidDataException(@NonNull final ProfileReadResponse response) {
+ this.response = response;
+ }
+
+ public ProfileReadResponse getResponse() {
+ return response;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/exception/InvalidRequestException.java b/app/src/main/java/no/nordicsemi/android/ble/exception/InvalidRequestException.java
new file mode 100644
index 0000000..3ab4d09
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/exception/InvalidRequestException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.exception;
+
+import no.nordicsemi.android.ble.Request;
+
+@SuppressWarnings({"WeakerAccess", "unused"})
+public final class InvalidRequestException extends Exception {
+ private final Request request;
+
+ public InvalidRequestException(final Request request) {
+ super("Invalid request");
+ this.request = request;
+ }
+
+ /**
+ * Returns the invalid request.
+ * @return The invalid request.
+ */
+ public Request getRequest() {
+ return request;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/exception/RequestFailedException.java b/app/src/main/java/no/nordicsemi/android/ble/exception/RequestFailedException.java
new file mode 100644
index 0000000..677798a
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/exception/RequestFailedException.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.exception;
+
+import no.nordicsemi.android.ble.Request;
+
+@SuppressWarnings({"WeakerAccess", "unused"})
+public final class RequestFailedException extends Exception {
+ private final Request request;
+ private final int status;
+
+ public RequestFailedException(final Request request, final int status) {
+ super("Request failed with status " + status);
+ this.request = request;
+ this.status = status;
+ }
+
+ /**
+ * Returns the request status. One of {{@link android.bluetooth.BluetoothGatt}} GATT_*
+ * or {@link no.nordicsemi.android.ble.callback.FailCallback} REASON_* codes.
+ *
+ * @return Error code.
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * Returns the request that failed.
+ * @return The request that failed.
+ */
+ public Request getRequest() {
+ return request;
+ }
+}
diff --git a/app/src/main/java/no/nordicsemi/android/ble/response/ConnectionPriorityResponse.java b/app/src/main/java/no/nordicsemi/android/ble/response/ConnectionPriorityResponse.java
new file mode 100644
index 0000000..bdabfda
--- /dev/null
+++ b/app/src/main/java/no/nordicsemi/android/ble/response/ConnectionPriorityResponse.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2018, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package no.nordicsemi.android.ble.response;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import no.nordicsemi.android.ble.callback.ConnectionPriorityCallback;
+
+/**
+ * The synchronous response type for connection priority requests.
+ *
+ * @see ConnectionPriorityCallback
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class ConnectionPriorityResponse implements ConnectionPriorityCallback, Parcelable {
+ private BluetoothDevice device;
+
+ @IntRange(from = 6, to = 3200)
+ private int interval;
+
+ @IntRange(from = 0, to = 499)
+ private int latency;
+
+ @IntRange(from = 10, to = 3200)
+ private int supervisionTimeout;
+
+ @Override
+ public void onConnectionUpdated(@NonNull final BluetoothDevice device,
+ @IntRange(from = 6, to = 3200) final int interval,
+ @IntRange(from = 0, to = 499) final int latency,
+ @IntRange(from = 10, to = 3200) final int timeout) {
+ this.device = device;
+ this.interval = interval;
+ this.latency = latency;
+ this.supervisionTimeout = timeout;
+ }
+
+ @Nullable
+ public BluetoothDevice getBluetoothDevice() {
+ return device;
+ }
+
+ /**
+ * The connection interval determines how often the Central will ask for data from the Peripheral.
+ * When the Peripheral requests an update, it supplies a maximum and a minimum wanted interval.
+ * The connection interval must be between 7.5 ms and 4 s.
+ *
+ * @return Connection interval used on this connection, 1.25ms unit.
+ * Valid range is from 6 (7.5ms) to 3200 (4000ms).
+ */
+ @IntRange(from = 6, to = 3200)
+ public int getConnectionInterval() {
+ return interval;
+ }
+
+ /**
+ * By setting a non-zero slave latency, the Peripheral can choose to not answer when
+ * the Central asks for data up to the slave latency number of times.
+ * However, if the Peripheral has data to send, it can choose to send data at any time.
+ * This enables a peripheral to stay sleeping for a longer time, if it doesn't have data to send,
+ * but still send data fast if needed. The text book example of such device is for example
+ * keyboard and mice, which want to be sleeping for as long as possible when there is
+ * no data to send, but still have low latency (and for the mouse: low connection interval)
+ * when needed.
+ *
+ * @return Slave latency for the connection in number of connection events.
+ * Valid range is from 0 to 499.
+ */
+ @IntRange(from = 0, to = 499)
+ public int getSlaveLatency() {
+ return latency;
+ }
+
+ /**
+ * This timeout determines the timeout from the last data exchange till a link is considered lost.
+ * A Central will not start trying to reconnect before the timeout has passed,
+ * so if you have a device which goes in and out of range often, and you need to notice when
+ * that happens, it might make sense to have a short timeout.
+ *
+ * @return Supervision timeout for this connection, in 10ms unit.
+ * Valid range is from 10 (100 ms = 0.1s) to 3200 (32s).
+ */
+ @IntRange(from = 10, to = 3200)
+ public int getSupervisionTimeout() {
+ return supervisionTimeout;
+ }
+
+ // Parcelable
+ protected ConnectionPriorityResponse(final Parcel in) {
+ device = in.readParcelable(BluetoothDevice.class.getClassLoader());
+ interval = in.readInt();
+ latency = in.readInt();
+ supervisionTimeout = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeParcelable(device, flags);
+ dest.writeInt(interval);
+ dest.writeInt(latency);
+ dest.writeInt(supervisionTimeout);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator