diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fd45b12
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches/build_file_checksums.ser
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser
new file mode 100644
index 0000000..d071ee8
Binary files /dev/null and b/.idea/caches/gradle_models.ser differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..681f41a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..15a15b2
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..88d1552
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..c3c9f49
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..14f1af3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,216 @@
+# FastBleGatt
+
+## 版本更新信息
+
+### v1.0.0
+
+- 正式版提交
+
+## 具体使用:
+
+### 1,引入库
+
+添加JitPack仓库到项目中
+
+项目根 build.gradle
+
+```groovy
+allprojects {
+ repositories {
+ ...
+ maven { url 'https://jitpack.io' }
+ }
+}
+```
+
+添加依赖,app模块中
+
+```groovy
+dependencies {
+ implementation 'com.github.shengMR:FastBleGatt:v1.0.0'
+}
+```
+
+### 2,使用
+
+1. 初始化蓝牙管理器
+
+ ```java
+ FastBleManager.getInstance().init(this);
+ ```
+
+2. 获取一个蓝牙关联器 FastBleGatt,之后所有操作都是对这个蓝牙关联器进行操作
+
+ ```java
+ // 参数:device,扫描到的蓝牙BluetoothDevice对象
+ FastBleGatt fastBleGatt = FastBleManager.getInstance().with(device);
+ ```
+
+3. 通过蓝牙关联器设置回调并开始连接
+
+ ```java
+ FastBleManager.getInstance()
+ .with(device)
+ .setBleCallback(new BleCallback() {
+ @Override
+ public void onConnecting(BluetoothDevice device) {
+ // 设备连接中
+ }
+
+ @Override
+ public void onConnected(BluetoothDevice device) {
+ // 设备连接成功
+ }
+
+ @Override
+ public void onDeviceReady(BluetoothDevice device) {
+ // 设备准备完毕,此时可以发送命令了
+ }
+
+ @Override
+ public void onDisconnecting(BluetoothDevice device) {
+ // 设备断开中
+ }
+
+ @Override
+ public void onDisconnectByUser(BluetoothDevice device) {
+ // 设备主动断开
+ }
+
+ @Override
+ public void onDisconnected(BluetoothDevice device) {
+ // 设备被动断开
+ }
+
+ @Override
+ public void onConnectTimeout(BluetoothDevice device) {
+ // 设备连接超时
+ }
+ })
+ .connect();
+ ```
+
+4. 通过蓝牙关联器对指定蓝牙设备进行操作
+
+ - 开启通知
+
+ ```java
+ // 创建一条命令
+ Request request = Request.newEnableNotifyRequest(
+ serviceUUID, // 服务UUID
+ characteristicUuid, // 特征UUID
+ new RequestCallback() { // 请求命令回调
+ @Override
+ public void success(FastBleGatt fastBleGatt, Request request, Object data) {
+ // 发送成功
+ }
+
+ @Override
+ public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) {
+ // 发送失败
+ }
+
+ @Override
+ public boolean timeout(FastBleGatt fastBleGatt, Request request) {
+ // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令
+ return false;
+ }
+ });
+
+ // 对指定设备做操作
+ FastBleManager.getInstance()
+ .with(device)
+ .sendRequest(request);
+ ```
+
+ - 写操作
+
+ ```java
+ // 创建一条命令
+ Request request = Request.newReadRequest(
+ serviceUUID, // 服务UUID
+ characteristicUuid, // 特征UUID
+ new RequestCallback() { // 请求命令回调
+ @Override
+ public void success(FastBleGatt fastBleGatt, Request request, Object data) {
+ // 发送成功
+ }
+
+ @Override
+ public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) {
+ // 发送失败
+ }
+
+ @Override
+ public boolean timeout(FastBleGatt fastBleGatt, Request request) {
+ // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令
+ return false;
+ }
+ });
+
+ // 对指定设备做操作
+ FastBleManager.getInstance()
+ .with(device).sendRequest(request);
+ ```
+
+ - 读操作
+
+ ```java
+ // 创建一条命令
+ Request request = Request.newReadRequest(
+ serviceUUID, // 服务UUID
+ characteristicUuid, // 特征UUID
+ data, // 需要写的数据
+ new RequestCallback() { // 请求命令回调
+ @Override
+ public void success(FastBleGatt fastBleGatt, Request request, Object data) {
+ // 发送成功
+ }
+
+ @Override
+ public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) {
+ // 发送失败
+ }
+
+ @Override
+ public boolean timeout(FastBleGatt fastBleGatt, Request request) {
+ // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令
+ return false;
+ }
+ });
+
+ // 对指定设备做操作
+ FastBleManager.getInstance()
+ .with(device).sendRequest(request);
+ ```
+
+## 基本概念
+
+### 1,Request消息对象
+
+ 我们要发送一个消息,需要创建一个请求对象,即Request,可用类方法直接构造出来,然后通过蓝牙关联器对指定设备进行发送
+
+```java
+Request.newWriteRequest(); // 写操作
+Request.newReadRequest(); // 读操作
+Request.newMtuRequest(); // 申请MTU操作
+Request.newEnableNotifyRequest(); // 开启通知
+Request.newDisableNotifyRequest(); // 关闭通知
+```
+
+每个Request消息都会要求输入一个RequestCallback对象,用来反馈消息的发送成功与否
+
+#### 消息特性:
+
+* 延时发送
+
+ 每一个Request对象都可以设置延时的时间,不过延时的时间相对于上一条命令
+
+ 如:request(1)延时200ms,request(2)延时300ms,然后两条一起发送,命令执行如下:
+
+ sendRequest ---- 间隔200ms ---- request(1)发送 ---- 成功/失败 ---- 延时300ms --- request(2)发送
+
+* 消息Tag
+
+ 每一个Request对象都可以携带一个Tag变量,用来发送成功失败判断是哪一个包的失败与发送
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..822343f
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.cys.fastblegatt"
+ minSdkVersion 19
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'com.google.code.gson:gson:2.8.5'
+
+ implementation 'com.github.shengMR:FastBleScan:master-SNAPSHOT'
+ implementation 'com.github.shengMR:BasisUtil:v1.0.1'
+ implementation project(path: ':fastblegatt')
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ff27d7f
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/cys/DiscoverServicesActivity.java b/app/src/main/java/com/cys/DiscoverServicesActivity.java
new file mode 100644
index 0000000..5625045
--- /dev/null
+++ b/app/src/main/java/com/cys/DiscoverServicesActivity.java
@@ -0,0 +1,348 @@
+package com.cys;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.cys.basis.view.helper.adapter.BaseViewHolder;
+import com.cys.fastblegatt.FastBleGatt;
+import com.cys.fastblegatt.FastBleManager;
+import com.cys.fastblegatt.callback.BleCallback;
+import com.cys.fastblegatt.callback.RequestCallback;
+import com.cys.fastblegatt.request.Request;
+import com.cys.fastblegatt.util.Logger;
+import com.cys.fastblegatt.util.PrintHelpper;
+import com.cys.fastblescan.bean.ScanDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DiscoverServicesActivity extends AppCompatActivity {
+
+ private ExpandableListView idElvService;
+ private ElvAdapter adapterService;
+ private FastBleGatt currentGatt;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_discover_services);
+
+
+ if (getIntent().hasExtra("CurrentDevice")) {
+ final ScanDevice scanDevice = getIntent().getParcelableExtra("CurrentDevice");
+ currentGatt = FastBleManager.getInstance().with(scanDevice.device);
+ currentGatt
+ .setBleCallback(new BleCallback() {
+ @Override
+ public void onConnecting(BluetoothDevice device) {
+ Logger.d("连接中 = " + device.getAddress());
+ }
+
+ @Override
+ public void onConnected(BluetoothDevice device) {
+ Logger.d("连接成功 = " + device.getAddress());
+ }
+
+ @Override
+ public void onDeviceReady(BluetoothDevice device) {
+ Logger.d("设备准备完毕 = " + device.getAddress());
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (currentGatt == null) {
+ return;
+ }
+ if (adapterService != null) {
+ adapterService.replaceData(currentGatt.getGattServices());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onDisconnecting(BluetoothDevice device) {
+ Logger.d("设备断开中 = " + device.getAddress());
+ }
+
+ @Override
+ public void onDisconnectByUser(BluetoothDevice device) {
+ Logger.d("设备主动断开 = " + device.getAddress());
+ }
+
+ @Override
+ public void onDisconnected(BluetoothDevice device) {
+ Logger.d("设备被动断开 = " + device.getAddress());
+ finish();
+ }
+
+ @Override
+ public void onConnectTimeout(BluetoothDevice device) {
+ finish();
+ }
+ })
+ .connect();
+ }
+
+ idElvService = findViewById(R.id.id_elv);
+ idElvService.setGroupIndicator(null);
+ adapterService = new ElvAdapter(this);
+ idElvService.setAdapter(adapterService);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (currentGatt != null) {
+ currentGatt.disconnect();
+ }
+ }
+
+ class ElvAdapter extends BaseExpandableListAdapter {
+
+ public Context mContext;
+ public LayoutInflater mInflater;
+ public List mService = new ArrayList<>();
+
+ public void replaceData(List services) {
+ if (mService != services) {
+ mService.clear();
+ mService.addAll(services);
+ }
+ notifyDataSetChanged();
+ }
+
+ public ElvAdapter(Context context) {
+ this.mContext = context.getApplicationContext();
+ this.mInflater = LayoutInflater.from(this.mContext);
+ }
+
+ @Override
+ public int getGroupCount() {
+ return mService.size();
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ return mService.get(groupPosition).getCharacteristics().size();
+ }
+
+ @Override
+ public BluetoothGattService getGroup(int groupPosition) {
+ return mService.get(groupPosition);
+ }
+
+ @Override
+ public BluetoothGattCharacteristic getChild(int groupPosition, int childPosition) {
+ return mService.get(groupPosition).getCharacteristics().get(childPosition);
+ }
+
+ @Override
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+ BaseViewHolder viewHolder = BaseViewHolder.getViewHolder(this.mContext, R.layout.adapter_item_service, groupPosition, convertView, parent);
+ viewHolder.setText(R.id.id_tv_service_uuid, "服务 -> " + adapterService.mService.get(groupPosition).getUuid().toString());
+ if (isExpanded) {
+ viewHolder.setImageResource(R.id.id_iv_arrow, R.drawable.ic_arrow_down);
+ } else {
+ viewHolder.setImageResource(R.id.id_iv_arrow, R.drawable.ic_arrow_up);
+ }
+ return viewHolder.getItemView();
+ }
+
+ @SuppressLint("SetTextI18n")
+ @Override
+ public View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
+ final BluetoothGattCharacteristic bean = adapterService.mService.get(groupPosition).getCharacteristics().get(childPosition);
+ BaseViewHolder viewHolder = BaseViewHolder.getViewHolder(this.mContext, R.layout.adapter_item_characteristic, childPosition, convertView, parent);
+ viewHolder.setText(R.id.id_tv_characteritic_uuid, "特征 -> " + bean.getUuid().toString());
+ TextView readTv = viewHolder.getView(R.id.id_tv_read);
+ Button readBt = viewHolder.getView(R.id.id_read);
+ Button writeBt = viewHolder.getView(R.id.id_write);
+ Button writeNRBt = viewHolder.getView(R.id.id_write_no_response);
+ Button notifyBt = viewHolder.getView(R.id.id_notify);
+ readTv.setVisibility(View.GONE);
+ readBt.setVisibility(View.GONE);
+ writeBt.setVisibility(View.GONE);
+ writeNRBt.setVisibility(View.GONE);
+ notifyBt.setVisibility(View.GONE);
+
+ // 读按钮
+ readBt.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Request request = Request.newReadRequest(adapterService.mService.get(groupPosition).getUuid(), bean.getUuid(), new RequestCallback() {
+ @Override
+ public void success(FastBleGatt baseFastGatt, Request request, Object data) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DiscoverServicesActivity.this, "读取成功", Toast.LENGTH_SHORT).show();
+ adapterService.notifyDataSetChanged();
+ }
+ });
+ }
+
+ @Override
+ public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DiscoverServicesActivity.this, "读取失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public boolean timeout(FastBleGatt baseFastGatt, Request request) {
+ return false;
+ }
+ });
+ currentGatt.sendRequest(request);
+ }
+ });
+
+ writeBt.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ byte[] data = new byte[]{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00/*, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06*/
+ };
+ List requests = FastBleManager.getInstance().calculRequest(
+ adapterService.mService.get(groupPosition).getUuid(),
+ bean.getUuid(),
+ data,
+ currentGatt.getBleDataSize(),
+ new RequestCallback() {
+ @Override
+ public void success(FastBleGatt baseFastGatt, Request request, Object data) {
+ Logger.d("发送成功:" + "Tag = " + request.tag + " " + PrintHelpper.bytesToHexString(request.data, ","));
+ }
+
+ @Override
+ public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) {
+
+ }
+
+ @Override
+ public boolean timeout(FastBleGatt baseFastGatt, Request request) {
+ return false;
+ }
+ });
+ Logger.d("request length = " + requests.size());
+ for (int i = 0; i < requests.size(); i++) {
+ Logger.d(String.format("request data[%d] = %s", i, PrintHelpper.bytesToHexString(requests.get(i).data, ",")));
+ }
+
+ currentGatt.sendRequests(requests);
+ }
+ });
+
+ // 通知按钮
+ notifyBt.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Request request = Request.newEnableNotifyRequest(adapterService.mService.get(groupPosition).getUuid(), bean.getUuid(), new RequestCallback() {
+ @Override
+ public void success(FastBleGatt baseFastGatt, Request request, Object data) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DiscoverServicesActivity.this, "成功", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DiscoverServicesActivity.this, "失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public boolean timeout(FastBleGatt baseFastGatt, Request request) {
+ return false;
+ }
+ });
+ currentGatt.sendRequest(request);
+ }
+ });
+
+
+ // 解析属性
+ int properties = bean.getProperties();
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) == BluetoothGattCharacteristic.PROPERTY_BROADCAST) {
+
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == BluetoothGattCharacteristic.PROPERTY_READ) {
+ readBt.setVisibility(View.VISIBLE);
+ if (bean.getValue() != null) {
+ readTv.setVisibility(View.VISIBLE);
+
+ readTv.setText("[" + PrintHelpper.bytesToHexString(bean.getValue(), ",") + "]" + new String(bean.getValue()));
+ }
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) {
+ writeNRBt.setVisibility(View.VISIBLE);
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) == BluetoothGattCharacteristic.PROPERTY_WRITE) {
+ writeBt.setVisibility(View.VISIBLE);
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
+ notifyBt.setVisibility(View.VISIBLE);
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
+
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) == BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) {
+
+ }
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) == BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) {
+
+ }
+
+
+ return viewHolder.getItemView();
+ }
+
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/com/cys/MainActivity.java b/app/src/main/java/com/cys/MainActivity.java
new file mode 100644
index 0000000..f2ca8d5
--- /dev/null
+++ b/app/src/main/java/com/cys/MainActivity.java
@@ -0,0 +1,245 @@
+package com.cys;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.cys.adapter.DeviceAdapter;
+import com.cys.fastblegatt.FastBleManager;
+import com.cys.fastblegatt.util.Logger;
+import com.cys.fastblescan.FastBleScanner;
+import com.cys.fastblescan.bean.ScanDevice;
+import com.cys.fastblescan.callback.FastBleScanCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MainActivity extends AppCompatActivity implements View.OnClickListener,
+ FastBleScanCallback, DeviceAdapter.OnItemClickListener {
+
+ private RecyclerView idRcvDevice;
+ private List mDeviceList = new ArrayList<>();
+ private DeviceAdapter adapterDevice;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ Logger.isDebug = true;
+
+ if (!FastBleScanner.getInstance().isSupportBluetooth()) {
+ Toast.makeText(this, "本设备不支持蓝牙", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1001);
+ }
+
+ findViewById(R.id.id_bt_start_scan).setOnClickListener(this);
+ findViewById(R.id.id_bt_stop_scan).setOnClickListener(this);
+ findViewById(R.id.id_bt_disconnect).setOnClickListener(this);
+
+ idRcvDevice = findViewById(R.id.id_rcv_device);
+ idRcvDevice.setLayoutManager(new LinearLayoutManager(this));
+ idRcvDevice.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
+ adapterDevice = new DeviceAdapter();
+ idRcvDevice.setAdapter(adapterDevice);
+ adapterDevice.setOnItemClickListener(this);
+
+ FastBleScanner.getInstance().setScanCallback(this);
+ FastBleManager.getInstance().init(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (!FastBleScanner.getInstance().isBluetoothEnabled()) {
+ FastBleScanner.getInstance().enableBluetooth();
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.id_bt_start_scan) {
+ if (FastBleScanner.getInstance().isBluetoothEnabled()) {
+ mDeviceList.clear();
+ adapterDevice.replaceData(mDeviceList);
+ FastBleScanner.getInstance().setIgnoreSame(true).startScan();
+ }
+ } else if (v.getId() == R.id.id_bt_stop_scan) {
+ if (FastBleScanner.getInstance().isBluetoothEnabled()) {
+ FastBleScanner.getInstance().stopScan();
+ }
+ } else if (v.getId() == R.id.id_bt_disconnect) {
+
+ }
+ }
+
+ @Override
+ public void onStartScan() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(MainActivity.this, "开始扫描", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onLeScan(final ScanDevice scanDevice) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.d("Sheng", "扫描 -> " + scanDevice.deviceName);
+ mDeviceList.add(scanDevice);
+ adapterDevice.replaceData(mDeviceList);
+ }
+ });
+ }
+
+ @Override
+ public void onStopScan() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(MainActivity.this, "停止扫描", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onScanFailure(int errorCode) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(MainActivity.this, "扫描出错", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onItemClick(ScanDevice scanDevice) {
+ if (FastBleScanner.getInstance().isBluetoothEnabled()) {
+ FastBleScanner.getInstance().stopScan();
+ }
+ Intent intent = new Intent(this, DiscoverServicesActivity.class);
+ intent.putExtra("CurrentDevice", scanDevice);
+ startActivity(intent);
+ }
+
+ /*
+ public void onDeviceReady() {
+ Request requestEnableNotify = Request.newWriteRequest(mServiceUuid, mCharactiesticUuid, new byte[]{0x01}, new RequestCallback() {
+ @Override
+ public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) {
+ mLogger.d("开启通知 发送成功");
+ }
+
+ @Override
+ public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) {
+ mLogger.d("开启通知 发送失败");
+ }
+
+ @Override
+ public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) {
+ mLogger.d("开启通知 发送超时");
+ return false;
+ }
+ });
+
+ mFastGatt.sendRequest(requestEnableNotify);
+
+ Request request2EnableNotify = Request.newWriteRequest(mServiceUuid, mCharactiesticUuid, new byte[]{0x01}, new RequestCallback() {
+ @Override
+ public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) {
+ mLogger.d("开启通知 发送成功");
+ }
+
+ @Override
+ public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) {
+ mLogger.d("开启通知 发送失败");
+ }
+
+ @Override
+ public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) {
+ mLogger.d("开启通知 发送超时");
+ return false;
+ }
+ }, 4000);
+
+ mFastGatt.sendRequest(request2EnableNotify);
+
+ for (int i = 513; i < 530; i ++) {
+ Request requestMtu = Request.newMtuRequest(
+ UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1910"),
+ UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1911"), i, new RequestCallback() {
+ @Override
+ public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) {
+ Logger.d("Mtu 发送成功" + (int) request.getTag());
+ }
+
+ @Override
+ public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) {
+ Logger.d("Mtu 发送失败");
+ }
+
+ @Override
+ public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) {
+ Logger.d("Mtu 发送超时" + (int) request.getTag());
+ return false;
+ }
+ });
+ requestMtu.setTag(i);
+ FastBleGatt.getInstance().sendRequest(requestMtu);
+ }
+
+ byte[] data = new byte[]{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06
+ };
+ List requests = FastBleGatt.getInstance().calculData(
+ UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1910"),
+ UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1911"),
+ data, new RequestCallback() {
+ @Override
+ public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) {
+ Logger.d("发送成功" + ArraysUtils.bytesToHexString(request.data, ","));
+ }
+
+ @Override
+ public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) {
+
+ }
+
+ @Override
+ public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) {
+ return false;
+ }
+ });
+ Logger.d("request length = " + requests.size());
+ for (int i = 0; i < requests.size(); i++) {
+ Logger.d(String.format("request data[%d] = %s", i, ArraysUtils.bytesToHexString(requests.get(i).data, ",")));
+ }
+
+ FastBleGatt.getInstance().sendCalcuData(requests);
+
+ }
+ */
+
+}
diff --git a/app/src/main/java/com/cys/adapter/DeviceAdapter.java b/app/src/main/java/com/cys/adapter/DeviceAdapter.java
new file mode 100644
index 0000000..a62bc61
--- /dev/null
+++ b/app/src/main/java/com/cys/adapter/DeviceAdapter.java
@@ -0,0 +1,66 @@
+package com.cys.adapter;
+
+import android.bluetooth.BluetoothDevice;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.cys.R;
+import com.cys.fastblescan.bean.ScanDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DeviceAdapter extends RecyclerView.Adapter {
+
+ public List mDatas = new ArrayList<>();
+
+ public interface OnItemClickListener{
+ void onItemClick(ScanDevice scanDevice);
+ }
+
+ public OnItemClickListener mItemClickListener;
+
+ public void setOnItemClickListener(OnItemClickListener clickListener){
+ this.mItemClickListener = clickListener;
+ }
+
+ public void replaceData(List datas) {
+ this.mDatas.clear();
+ this.mDatas.addAll(datas);
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public DeviceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_device, parent, false);
+ return new DeviceViewHolder(inflate);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull DeviceViewHolder holder, int position) {
+ final ScanDevice scanDevice = mDatas.get(position);
+ BluetoothDevice device = scanDevice.device;
+ holder.textViewByName.setText((TextUtils.isEmpty(scanDevice.deviceName) ? device.getAddress() : scanDevice.deviceName)
+ + "( rssi " + scanDevice.rssi + " ) ");
+ holder.textViewByMac.setText("Mac : " + device.getAddress());
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if(mItemClickListener != null){
+ mItemClickListener.onItemClick(scanDevice);
+ }
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDatas.size();
+ }
+}
diff --git a/app/src/main/java/com/cys/adapter/DeviceViewHolder.java b/app/src/main/java/com/cys/adapter/DeviceViewHolder.java
new file mode 100644
index 0000000..785d7ad
--- /dev/null
+++ b/app/src/main/java/com/cys/adapter/DeviceViewHolder.java
@@ -0,0 +1,20 @@
+package com.cys.adapter;
+
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+import com.cys.R;
+
+
+public class DeviceViewHolder extends RecyclerView.ViewHolder {
+
+ public TextView textViewByName;
+ public TextView textViewByMac;
+
+ public DeviceViewHolder(View itemView) {
+ super(itemView);
+ textViewByName = itemView.findViewById(R.id.id_tv_name);
+ textViewByMac = itemView.findViewById(R.id.id_tv_mac);
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png
new file mode 100644
index 0000000..08e107f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png
new file mode 100644
index 0000000..3911c26
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_discover_services.xml b/app/src/main/res/layout/activity_discover_services.xml
new file mode 100644
index 0000000..c445564
--- /dev/null
+++ b/app/src/main/res/layout/activity_discover_services.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..0bb9f33
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_item_characteristic.xml b/app/src/main/res/layout/adapter_item_characteristic.xml
new file mode 100644
index 0000000..bed0fa3
--- /dev/null
+++ b/app/src/main/res/layout/adapter_item_characteristic.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_item_device.xml b/app/src/main/res/layout/adapter_item_device.xml
new file mode 100644
index 0000000..ebeffb0
--- /dev/null
+++ b/app/src/main/res/layout/adapter_item_device.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_item_service.xml b/app/src/main/res/layout/adapter_item_service.xml
new file mode 100644
index 0000000..a7e6eb8
--- /dev/null
+++ b/app/src/main/res/layout/adapter_item_service.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..0d03a2a
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #4c8492
+ #4c8492
+ #fffde8
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ce1f48a
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ FastBleGatt
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..95812ce
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+ maven { url 'https://jitpack.io' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.6.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ maven { url 'https://jitpack.io' }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/fastblegatt/.gitignore b/fastblegatt/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/fastblegatt/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/fastblegatt/build.gradle b/fastblegatt/build.gradle
new file mode 100644
index 0000000..f2b6743
--- /dev/null
+++ b/fastblegatt/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ minSdkVersion 19
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'com.github.shengMR:BasisUtil:v1.0.1'
+}
diff --git a/fastblegatt/proguard-rules.pro b/fastblegatt/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/fastblegatt/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/fastblegatt/src/main/AndroidManifest.xml b/fastblegatt/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1b26041
--- /dev/null
+++ b/fastblegatt/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/FastBleGatt.java b/fastblegatt/src/main/java/com/cys/fastblegatt/FastBleGatt.java
new file mode 100644
index 0000000..6415271
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/FastBleGatt.java
@@ -0,0 +1,869 @@
+package com.cys.fastblegatt;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.cys.fastblegatt.callback.BleCallback;
+import com.cys.fastblegatt.callback.BleCallbackAdapterListener;
+import com.cys.fastblegatt.callback.RequestCallback;
+import com.cys.fastblegatt.request.Request;
+import com.cys.fastblegatt.util.Logger;
+import com.cys.fastblegatt.util.PrintHelpper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FastBleGatt {
+
+ public static String TAG = FastBleGatt.class.getSimpleName();
+
+ private Context mContext;
+ private BluetoothDevice mDevice;
+ private BluetoothGatt mBluetoothGatt;
+ private List mGattServices = new ArrayList<>();
+ private int mConnectStatus = BluetoothAdapter.STATE_DISCONNECTED;
+ private BluetoothGattCallback mGattCallback;
+ private BleCallback mBleCallback;
+ private BleCallbackAdapterListener mBleCallbackAdapterListener;
+ private int mBleDataSize = 20;
+ private long mDefaultTimeoutDelay = 10 * 1000L;
+ private boolean mDescriptorNotify = false;
+
+ private final LinkedBlockingDeque mInBufferedDeque = new LinkedBlockingDeque();
+ private final LinkedBlockingDeque mOutBufferedDeque = new LinkedBlockingDeque();
+ private AtomicBoolean isPostRequesting = new AtomicBoolean(false);
+ private Handler mConnectTimeoutHandler = new Handler(Looper.getMainLooper());
+ private Handler mPostTimeoutHandler = new Handler(Looper.getMainLooper());
+ private Handler mPostDelayHandler = new Handler(Looper.getMainLooper());
+ private final Object mStatusLock = new Object();
+
+ //region Task
+ public Runnable mConneciTimeoutTask = new Runnable() {
+ @Override
+ public void run() {
+ if (!isConnected()) {
+ onDeviceConnectTimeout(mDevice);
+ }
+ }
+ };
+
+ public Runnable mPostDelayTask = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mOutBufferedDeque) {
+ Request request = mOutBufferedDeque.peek();
+ realSendRequest(request);
+ }
+ }
+ };
+
+ public Runnable mPostTimeoutTask = new Runnable() {
+
+ @Override
+ public void run() {
+ synchronized (mOutBufferedDeque) {
+
+ // 得到队头的对象
+ Request request = mOutBufferedDeque.peek();
+ if (request != null) {
+
+ Logger.d("send request timeout");
+ boolean retry = requestTimeout(request);
+
+ if (retry) {
+ realSendRequest(request);
+ } else {
+ mOutBufferedDeque.poll();
+ requestCompleted();
+ }
+ }
+ }
+ }
+ };
+ //endregion
+
+ public FastBleGatt(Context context, BluetoothDevice device) {
+ this.mContext = context;
+ this.mGattCallback = new BleGattCallback();
+ this.mDevice = device;
+ }
+
+ public FastBleGatt setBleCallback(BleCallback callback) {
+ this.mBleCallback = callback;
+ return this;
+ }
+
+ public FastBleGatt setBleCallbackAdapterListener(BleCallbackAdapterListener listener) {
+ this.mBleCallbackAdapterListener = listener;
+ return this;
+ }
+
+ public FastBleGatt setDescriptorNotify(boolean mSetDescriptorNotify) {
+ this.mDescriptorNotify = mSetDescriptorNotify;
+ return this;
+ }
+
+ public boolean isConnected() {
+ return mConnectStatus == BluetoothAdapter.STATE_CONNECTED;
+ }
+
+ public List getGattServices() {
+ return mGattServices;
+ }
+
+ public int getBleDataSize() {
+ return mBleDataSize;
+ }
+
+ public boolean connect() {
+ return connect(mDefaultTimeoutDelay);
+ }
+
+ public boolean connect(long delay) {
+
+ synchronized (mStatusLock) {
+ if (mConnectStatus == BluetoothAdapter.STATE_CONNECTED) {
+ return true;
+ }
+ mConnectStatus = BluetoothAdapter.STATE_CONNECTING;
+ }
+
+ close(mBluetoothGatt);
+
+ onDeviceConnecting(mDevice);
+
+ // 连接超时
+ mConnectTimeoutHandler.removeCallbacks(mConneciTimeoutTask);
+ mConnectTimeoutHandler.postDelayed(mConneciTimeoutTask, delay);
+ mBluetoothGatt = this.mDevice.connectGatt(this.mContext, false, mGattCallback);
+ return mBluetoothGatt != null;
+ }
+
+ public void disconnect() {
+ boolean connected = isConnected();
+ if (connected) {
+ synchronized (mStatusLock) {
+ mConnectStatus = BluetoothAdapter.STATE_DISCONNECTING;
+ }
+ onDeviceDisconnecting(mDevice);
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
+ } else {
+ if (mConnectStatus == BluetoothAdapter.STATE_CONNECTING) {
+ close(mBluetoothGatt);
+ reset();
+ }
+ }
+ }
+
+ private void close(BluetoothGatt gatt) {
+ if (gatt != null) {
+ gatt.close();
+ }
+ }
+
+ private void reset() {
+ synchronized (mOutBufferedDeque) {
+ mOutBufferedDeque.clear();
+ }
+ synchronized (mInBufferedDeque) {
+ mInBufferedDeque.clear();
+ }
+ isPostRequesting.set(false);
+ mConnectStatus = BluetoothAdapter.STATE_DISCONNECTED;
+ mConnectTimeoutHandler.removeCallbacksAndMessages(null);
+ mPostTimeoutHandler.removeCallbacksAndMessages(null);
+ mPostDelayHandler.removeCallbacksAndMessages(null);
+ }
+
+ //region 蓝牙操作
+ public void sendRequests(List requests) {
+ for (int i = 0; i < requests.size(); i++) {
+ sendRequest(requests.get(i));
+ }
+ }
+
+ public void sendRequest(Request request) {
+
+ if (request != null) {
+ if (!isConnected()) {
+ if (request.callback != null) {
+ request.callback.error(this, request, "The device is not connected");
+ }
+ return;
+ }
+ mInBufferedDeque.add(request);
+ // 上一条还没完成,直接保存在队列中然后直接return
+ if (isPostRequesting.get()) {
+ return;
+ }
+ isPostRequesting.set(true);
+ this.postRequest();
+ }
+ }
+
+ private void postRequest() {
+
+ Request request;
+
+ synchronized (mInBufferedDeque) {
+ if (mInBufferedDeque.isEmpty()) {
+ isPostRequesting.set(false);
+ return;
+ }
+ request = mInBufferedDeque.poll();
+ }
+
+ if (request == null) {
+ isPostRequesting.set(false);
+ return;
+ }
+
+ int requestType = request.requestType;
+ // 识别只有读写操作才需要添加到输出队列
+ if (mDescriptorNotify || requestType != Request.ENABLE_NOTIFY && requestType != Request.DISABLE_NOTIFY) {
+ synchronized (mOutBufferedDeque) {
+ mOutBufferedDeque.add(request);
+ }
+ }
+
+ if (request.delay > 0) {
+ mPostDelayHandler.postDelayed(mPostDelayTask, request.delay);
+ return;
+ }
+
+ this.realSendRequest(request);
+ }
+
+ synchronized private void realSendRequest(Request request) {
+
+ if (request == null) {
+ this.isPostRequesting.set(false);
+ return;
+ }
+
+ if (!isConnected()) {
+ reset();
+ return;
+ }
+
+ // 根据类型执行蓝牙操作
+ int type = request.requestType;
+ switch (type) {
+ case Request.WRITE:
+ setTimeoutFlag();
+ writeCharacteristic(request);
+ break;
+ case Request.READ:
+ setTimeoutFlag();
+ readCharacteristic(request);
+ break;
+ case Request.ENABLE_NOTIFY:
+ if (mDescriptorNotify) {
+ setTimeoutFlag();
+ }
+ enableNotification(request);
+ break;
+ case Request.DISABLE_NOTIFY:
+ if (mDescriptorNotify) {
+ setTimeoutFlag();
+ }
+ disableNotification(request);
+ break;
+ case Request.MTU:
+ setTimeoutFlag();
+ mtuChange(request);
+ break;
+ }
+ }
+
+ private void setTimeoutFlag() {
+ this.mPostTimeoutHandler.removeCallbacksAndMessages(null);
+ this.mPostTimeoutHandler.postDelayed(this.mPostTimeoutTask, 2000);
+ }
+
+ private void clearTimeoutFlag() {
+ this.mPostTimeoutHandler.removeCallbacksAndMessages(null);
+ }
+
+ private void writeCharacteristic(Request request) {
+
+ final BluetoothGatt mGatt = this.mBluetoothGatt;
+ boolean success = true;
+ String errorMsg = "";
+
+ final Request realRequest = request;
+
+ if (mGatt != null) {
+
+ BluetoothGattService service = mGatt.getService(realRequest.serviceUuid);
+
+ if (service != null) {
+ BluetoothGattCharacteristic characteristic = service
+ .getCharacteristic(realRequest.characteristicUuid);
+
+ if (characteristic != null) {
+
+ characteristic.setValue(request.data);
+
+ if (!mGatt.writeCharacteristic(characteristic)) {
+ success = false;
+ errorMsg = "write characteristic error";
+ }
+
+ } else {
+ success = false;
+ errorMsg = "device does not have this characteristic UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "device does not have this service UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "gatt does not exist";
+ }
+
+ if (!success) {
+ this.requestError(errorMsg);
+ this.requestCompleted();
+ }
+ }
+
+ private void readCharacteristic(Request request) {
+
+ final BluetoothGatt gatt = this.mBluetoothGatt;
+ boolean success = true;
+ String errorMsg = "";
+
+ final Request realRequest = request;
+
+ if (gatt != null) {
+ BluetoothGattService service = gatt.getService(realRequest.serviceUuid);
+ if (service != null) {
+
+ BluetoothGattCharacteristic characteristic = service
+ .getCharacteristic(realRequest.characteristicUuid);
+
+ if (characteristic != null) {
+
+ if (!gatt.readCharacteristic(characteristic)) {
+ success = false;
+ errorMsg = "read characteristic error";
+ }
+
+ } else {
+ success = false;
+ errorMsg = "device does not have this characteristic UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "device does not have this service UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "gatt does not exist";
+ }
+
+ if (!success) {
+ this.requestError(errorMsg);
+ this.requestCompleted();
+ }
+ }
+
+ private void enableNotification(Request request) {
+
+ final BluetoothGatt gatt = this.mBluetoothGatt;
+ boolean success = true;
+ String errorMsg = "";
+
+ final Request realRequest = request;
+
+ if (gatt != null) {
+
+ BluetoothGattService service = gatt.getService(realRequest.serviceUuid);
+
+ if (service != null) {
+
+ BluetoothGattCharacteristic characteristic = service
+ .getCharacteristic(realRequest.characteristicUuid);
+
+ if (characteristic != null) {
+
+ if (!gatt.setCharacteristicNotification(characteristic,
+ true)) {
+ success = false;
+ errorMsg = "open notification error";
+ } else {
+ if (mDescriptorNotify) {
+ if (characteristic.getDescriptors().size() > 0) {
+ List descriptors = characteristic.getDescriptors();
+ for (int i = 0; i < descriptors.size(); i++) {
+ BluetoothGattDescriptor descriptor = descriptors.get(i);
+ if (descriptor != null) {
+ //Write the description value
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ }
+ gatt.writeDescriptor(descriptor);
+ }
+ }
+ }
+ }
+ }
+
+
+ } else {
+ success = false;
+ errorMsg = "device does not have this characteristic UUID";
+ }
+
+ } else {
+ success = false;
+ errorMsg = "device does not have this service UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "gatt does not exist";
+ }
+
+ if (!success) {
+ if (mDescriptorNotify) {
+ this.requestError(errorMsg);
+ } else {
+ this.requestError(request, errorMsg);
+ }
+ this.requestCompleted();
+ }
+
+ if (!mDescriptorNotify) {
+ this.requestSuccess(request, "");
+ this.requestCompleted();
+ }
+ }
+
+ private void disableNotification(Request request) {
+ final BluetoothGatt gatt = this.mBluetoothGatt;
+ boolean success = true;
+ String errorMsg = "";
+
+ final Request realRequest = request;
+
+ if (gatt != null) {
+
+ BluetoothGattService service = gatt.getService(realRequest.serviceUuid);
+
+ if (service != null) {
+
+ BluetoothGattCharacteristic characteristic = service
+ .getCharacteristic(realRequest.characteristicUuid);
+
+ if (characteristic != null) {
+ if (!gatt.setCharacteristicNotification(characteristic,
+ false)) {
+ success = false;
+ errorMsg = "close notification error";
+ } else {
+ if (mDescriptorNotify) {
+ if (characteristic.getDescriptors().size() > 0) {
+ List descriptors = characteristic.getDescriptors();
+ for (int i = 0; i < descriptors.size(); i++) {
+ BluetoothGattDescriptor descriptor = descriptors.get(i);
+ if (descriptor != null) {
+ //Write the description value
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
+ descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
+ }
+ gatt.writeDescriptor(descriptor);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ success = false;
+ errorMsg = "device does not have this characteristic UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "device does not have this service UUID";
+ }
+ } else {
+ success = false;
+ errorMsg = "gatt does not exist";
+ }
+
+ if (!success) {
+ if (mDescriptorNotify) {
+ this.requestError(errorMsg);
+ } else {
+ this.requestError(request, errorMsg);
+ }
+ this.requestCompleted();
+ }
+
+ if (!mDescriptorNotify) {
+ this.requestSuccess(request, "");
+ this.requestCompleted();
+ }
+
+ }
+
+ private void mtuChange(Request request) {
+
+ final BluetoothGatt gatt = this.mBluetoothGatt;
+ boolean success = true;
+ String errorMsg = "";
+
+ final Request realRequest = request;
+
+ if (gatt != null) {
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
+ if (!gatt.requestMtu(realRequest.mtu)) {
+ success = false;
+ errorMsg = "request Mtu error";
+ }
+ }
+ } else {
+ success = false;
+ errorMsg = "gatt does not exist";
+ }
+
+ if (!success) {
+ this.requestError(errorMsg);
+ this.requestCompleted();
+ }
+
+ }
+
+ private void requestSuccess(Object data) {
+ Request request = this.mOutBufferedDeque.poll();
+ this.requestSuccess(request, data);
+ }
+
+ private void requestSuccess(Request request, Object data) {
+
+ if (request != null) {
+ RequestCallback callback = request.callback;
+ if (callback != null) {
+ callback.success(this, request,
+ data);
+ }
+ }
+ }
+
+ private void requestError(String errorMsg) {
+ Request request = this.mOutBufferedDeque.poll();
+ this.requestError(request, errorMsg);
+ }
+
+ private void requestError(Request request, String errorMsg) {
+ if (request != null) {
+ RequestCallback callback = request.callback;
+ if (callback != null) {
+ callback.error(this, request,
+ errorMsg);
+ }
+ }
+ }
+
+ private void requestCompleted() {
+
+ synchronized (mInBufferedDeque) {
+ if (this.mInBufferedDeque.isEmpty()) {
+ this.isPostRequesting.set(false);
+ } else {
+ this.postRequest();
+ }
+ }
+ }
+
+ private boolean requestTimeout(Request request) {
+
+ if (request != null) {
+ RequestCallback callback = request.callback;
+ if (callback != null) {
+ return callback.timeout(this, request);
+ }
+ }
+
+ return false;
+ }
+ //endregion
+
+ /**
+ * 蓝牙底层回调
+ */
+ public class BleGattCallback extends BluetoothGattCallback {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ super.onConnectionStateChange(gatt, status, newState);
+ Logger.d("onConnectionStateChange ----> " + " status = " + status + " newState = " + newState);
+ // status newState
+ // 0 2 -> 连接成功
+ // 0 0 -> 手动断开
+ // 133 0 -> 异常断开 : 可能以达到手机连接蓝牙最大数量,再次连接则失败,请使用Gatt.close() ,释放连接
+ if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
+ synchronized (mStatusLock) {
+ mConnectStatus = BluetoothAdapter.STATE_CONNECTED;
+ }
+ onDeviceConnected(mDevice);
+ Logger.d("discover service...");
+ gatt.discoverServices();
+ } else {
+ boolean disconnecting = mConnectStatus == BluetoothAdapter.STATE_DISCONNECTING;
+ synchronized (mStatusLock) {
+ mConnectStatus = BluetoothAdapter.STATE_DISCONNECTED;
+ }
+ if (disconnecting) {
+ onDeviceDisconnectByUser(mDevice);
+ } else {
+ onDeviceDisconnected(mDevice);
+ }
+ close(gatt);
+ reset();
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ super.onServicesDiscovered(gatt, status);
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Logger.d("discover service success");
+ List services = gatt.getServices();
+ mGattServices.clear();
+ mGattServices.addAll(services);
+ onGattServicesDiscovered(services);
+ } else {
+ Logger.d("discover service failure");
+ if (mBluetoothGatt != null) {
+ synchronized (mStatusLock) {
+ mConnectStatus = BluetoothGatt.STATE_DISCONNECTED;
+ }
+ onDeviceDisconnected(mDevice);
+ close(gatt);
+ reset();
+ }
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ super.onCharacteristicChanged(gatt, characteristic);
+ byte[] value = characteristic.getValue();
+ Logger.d("notify change:" + PrintHelpper.bytesToHexString(value, ","));
+ onGattCharacteristicChanged(value);
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ super.onCharacteristicRead(gatt, characteristic, status);
+ clearTimeoutFlag();
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ byte[] data = characteristic.getValue();
+ Logger.d("read characteristic success:" + PrintHelpper.bytesToHexString(data, ","));
+ requestSuccess(data);
+ onGattCharacteristicRead(data);
+ } else {
+ Logger.d("read characteristic failure");
+ requestError("read characteristic data error");
+ }
+
+ requestCompleted();
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ super.onCharacteristicWrite(gatt, characteristic, status);
+ clearTimeoutFlag();
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Logger.d("write characteristic success");
+ requestSuccess(null);
+ onGattCharacteristicWrite(characteristic.getValue());
+ } else {
+ Logger.d("write characteristic failure");
+ requestError("write characteristic data error");
+ }
+
+ requestCompleted();
+ }
+
+ @Override
+ public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ super.onDescriptorRead(gatt, descriptor, status);
+
+ clearTimeoutFlag();
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ byte[] data = descriptor.getValue();
+ Logger.d("read descriptor success:" + PrintHelpper.bytesToHexString(data, ","));
+ requestSuccess(data);
+ onGattDescriptorRead(descriptor.getValue());
+ } else {
+ Logger.d("read descriptor failure");
+ requestError("read descriptor data error");
+ }
+
+ requestCompleted();
+ }
+
+ @Override
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ super.onDescriptorWrite(gatt, descriptor, status);
+
+ clearTimeoutFlag();
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Logger.d("write descriptor success");
+ requestSuccess(null);
+ onGattDescriptorWrite(descriptor.getValue());
+ } else {
+ Logger.d("write descriptor failure");
+ requestError("write descriptor data error");
+ }
+
+ requestCompleted();
+ }
+
+ @Override
+ public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+ super.onMtuChanged(gatt, mtu, status);
+ clearTimeoutFlag();
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Logger.d("request mtu success:" + mtu);
+ // 需要出去ATT的Opcode一个字节和ATT的handle的两个字节
+ mBleDataSize = mtu - 3;
+ requestSuccess(null);
+ onGattMtuChange(mtu);
+ } else {
+ requestError("request mtu error");
+ }
+
+ requestCompleted();
+ }
+
+ @Override
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+ super.onReadRemoteRssi(gatt, rssi, status);
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Logger.d("read rssi success:" + rssi);
+ onGattRssiChange(rssi);
+ } else {
+ Logger.d("read rssi failure");
+ }
+ }
+ }
+
+ protected void onGattServicesDiscovered(List services) {
+ if (mBleCallback != null) {
+ mBleCallback.onDeviceReady(mDevice);
+ }
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onDeviceReady(mDevice);
+ }
+ }
+
+ protected void onGattCharacteristicChanged(byte[] value) {
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onNotify(mDevice, value);
+ }
+ }
+
+ protected void onGattCharacteristicRead(byte[] value) {
+
+ }
+
+ protected void onGattCharacteristicWrite(byte[] value) {
+
+ }
+
+ protected void onGattDescriptorRead(byte[] value) {
+
+ }
+
+ protected void onGattDescriptorWrite(byte[] value) {
+
+ }
+
+ protected void onGattMtuChange(int mtu) {
+
+ }
+
+ protected void onGattRssiChange(int rssi) {
+ }
+
+ protected void onDeviceConnecting(BluetoothDevice device) {
+ if (mBleCallback != null) {
+ mBleCallback.onConnecting(device);
+ }
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onConnecting(device);
+ }
+ }
+
+ protected void onDeviceConnected(BluetoothDevice device) {
+ if (mBleCallback != null) {
+ mBleCallback.onConnected(device);
+ }
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onConnected(device);
+ }
+ }
+
+ protected void onDeviceDisconnecting(BluetoothDevice device) {
+ if (mBleCallback != null) {
+ mBleCallback.onDisconnecting(device);
+ }
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onDisconnecting(mDevice);
+ }
+ }
+
+ protected void onDeviceDisconnectByUser(BluetoothDevice device) {
+ if (mBleCallback != null) {
+ mBleCallback.onDisconnectByUser(device);
+ }
+
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onDisconnectByUser(mDevice);
+ }
+ }
+
+ protected void onDeviceDisconnected(BluetoothDevice device) {
+ if (mBleCallback != null) {
+ mBleCallback.onDisconnected(device);
+ }
+
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onDisconnected(mDevice);
+ }
+ }
+
+ protected void onDeviceConnectTimeout(BluetoothDevice device) {
+ if (mBleCallback != null) {
+ mBleCallback.onConnectTimeout(device);
+ }
+
+ if (mBleCallbackAdapterListener != null) {
+ mBleCallbackAdapterListener.onConnectTimeout(mDevice);
+ }
+ }
+
+}
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/FastBleManager.java b/fastblegatt/src/main/java/com/cys/fastblegatt/FastBleManager.java
new file mode 100644
index 0000000..5c15e50
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/FastBleManager.java
@@ -0,0 +1,139 @@
+package com.cys.fastblegatt;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import com.cys.fastblegatt.callback.RequestCallback;
+import com.cys.fastblegatt.request.Request;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class FastBleManager {
+
+ public static String TAG = FastBleManager.class.getSimpleName();
+
+ private static FastBleManager mThis;
+ private Map mFastBleGattMap = new HashMap<>();
+ private Context mContext;
+
+ public static FastBleManager getInstance() {
+ if (mThis == null) {
+ synchronized (FastBleManager.class) {
+ if (mThis == null) {
+ mThis = new FastBleManager();
+ }
+ }
+ }
+ return mThis;
+ }
+
+ public void init(Context context) {
+ this.mContext = context;
+ }
+
+ /**
+ * 获取操作中的蓝牙设备集合(包括之前连接过断开的)
+ *
+ * @return
+ */
+ public Map getFastBleGattMap() {
+ return Collections.unmodifiableMap(mFastBleGattMap);
+ }
+
+ /**
+ * 关闭所有蓝牙连接
+ */
+ public void closeAllConnects() {
+ for (String s : mFastBleGattMap.keySet()) {
+ mFastBleGattMap.get(s).disconnect();
+ }
+ mFastBleGattMap.clear();
+ }
+
+ /**
+ * 关闭指定的蓝牙连接
+ *
+ * @param device 指定的蓝牙设备
+ */
+ public void closeConnect(BluetoothDevice device) {
+ if (mFastBleGattMap.containsKey(device.getAddress())) {
+ mFastBleGattMap.get(device.getAddress()).disconnect();
+ mFastBleGattMap.remove(device.getAddress());
+ }
+ }
+
+ /**
+ * 操作一个蓝牙设备
+ *
+ * @param device
+ * @return
+ */
+ public FastBleGatt with(BluetoothDevice device) {
+ if (mFastBleGattMap.containsKey(device.getAddress())) {
+ return mFastBleGattMap.get(device.getAddress());
+ }
+ FastBleGatt gatt = new FastBleGatt(this.mContext, device);
+ mFastBleGattMap.put(device.getAddress(), gatt);
+ return gatt;
+ }
+
+
+ /**
+ * 分包请求消息(不足一个包消息,补0x00在数据后面)
+ *
+ * @param serviceUuid 服务UUID
+ * @param characteristicUuid 特征UUID
+ * @param data 数据
+ * @param lineSize 每个消息的包大小
+ * @param callback 消息回调
+ * @return 消息列表
+ */
+ public List calculRequest(UUID serviceUuid, UUID characteristicUuid, byte[] data, int lineSize, RequestCallback callback) {
+
+ final byte[] sendData = data;
+ int mtuSize = lineSize;
+ if (mtuSize < Request.MTU_MIN - 3) {
+ mtuSize = Request.MTU_MIN - 3;
+ }
+ int packLength = 0;
+ if (sendData.length % mtuSize == 0) {
+ packLength = sendData.length / mtuSize;
+ } else {
+ packLength = sendData.length / mtuSize + 1;
+ }
+ List requests = new ArrayList<>();
+ int dataPosition = 0;
+ int dataTag = 0;
+ while (packLength > 1) {
+ byte[] newData = new byte[mtuSize];
+ System.arraycopy(sendData, dataPosition, newData, 0, mtuSize);
+ Request request = Request.newWriteRequest(serviceUuid, characteristicUuid, newData, callback);
+ request.tag = "Multipack[" + dataTag++ + "]";
+ requests.add(request);
+ packLength--;
+ dataPosition += mtuSize;
+ }
+ int offsetSize = sendData.length % mtuSize;
+ if (offsetSize != 0) {
+ byte[] newData = new byte[mtuSize];
+ System.arraycopy(sendData, dataPosition, newData, 0, offsetSize);
+ Request request = Request.newWriteRequest(serviceUuid, characteristicUuid, newData, callback);
+ request.tag = "Multipack[" + dataTag + "]";
+ requests.add(request);
+ } else {
+ byte[] newData = new byte[mtuSize];
+ System.arraycopy(sendData, dataPosition, newData, 0, mtuSize);
+ Request request = Request.newWriteRequest(serviceUuid, characteristicUuid, newData, callback);
+ request.tag = "Multipack[" + dataTag + "]";
+ requests.add(request);
+ }
+
+ return requests;
+ }
+
+}
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallback.java b/fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallback.java
new file mode 100644
index 0000000..7d6c039
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallback.java
@@ -0,0 +1,24 @@
+package com.cys.fastblegatt.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+public interface BleCallback {
+
+ void onConnecting(BluetoothDevice device);
+
+ void onConnected(BluetoothDevice device);
+
+ void onDeviceReady(BluetoothDevice device);
+
+ void onDisconnecting(BluetoothDevice device);
+
+ void onDisconnectByUser(BluetoothDevice device);
+
+ void onDisconnected(BluetoothDevice device);
+
+ void onConnectTimeout(BluetoothDevice device);
+
+ interface NotifyCallback {
+ void onNotify(BluetoothDevice device, byte[] data);
+ }
+}
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallbackAdapterListener.java b/fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallbackAdapterListener.java
new file mode 100644
index 0000000..2c47503
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallbackAdapterListener.java
@@ -0,0 +1,47 @@
+package com.cys.fastblegatt.callback;
+
+import android.bluetooth.BluetoothDevice;
+
+public abstract class BleCallbackAdapterListener implements BleCallback, BleCallback.NotifyCallback {
+
+ @Override
+ public void onConnecting(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onConnected(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onDeviceReady(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onDisconnecting(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onDisconnectByUser(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onDisconnected(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onConnectTimeout(BluetoothDevice device) {
+
+ }
+
+ @Override
+ public void onNotify(BluetoothDevice device, byte[] data) {
+
+ }
+}
+
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/callback/RequestCallback.java b/fastblegatt/src/main/java/com/cys/fastblegatt/callback/RequestCallback.java
new file mode 100644
index 0000000..1df9b13
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/callback/RequestCallback.java
@@ -0,0 +1,16 @@
+package com.cys.fastblegatt.callback;
+
+import com.cys.fastblegatt.FastBleGatt;
+import com.cys.fastblegatt.request.Request;
+
+/**
+ * 命令回调
+ */
+public interface RequestCallback {
+
+ void success(FastBleGatt fastBleGatt, Request request, Object data);
+
+ void error(FastBleGatt fastBleGatt, Request request, String errorMsg);
+
+ boolean timeout(FastBleGatt fastBleGatt, Request request);
+}
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/request/Request.java b/fastblegatt/src/main/java/com/cys/fastblegatt/request/Request.java
new file mode 100644
index 0000000..6250a85
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/request/Request.java
@@ -0,0 +1,126 @@
+package com.cys.fastblegatt.request;
+
+import androidx.annotation.IntDef;
+
+
+import com.cys.fastblegatt.callback.RequestCallback;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.UUID;
+
+public class Request {
+
+ @IntDef({WRITE, READ, MTU, ENABLE_NOTIFY, DISABLE_NOTIFY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestType {
+ }
+
+ public static final int WRITE = 0x01;
+ public static final int READ = 0x02;
+ public static final int MTU = 0x04;
+ public static final int ENABLE_NOTIFY = 0x08;
+ public static final int DISABLE_NOTIFY = 0x10;
+
+ public static final int MTU_MAX = 517;
+ public static final int MTU_MIN = 23;
+
+ public int requestType; // 对蓝牙进行操作的类型
+ public UUID serviceUuid; // 操作的服务UUID
+ public UUID characteristicUuid; // 操作的特征UUID
+ public byte[] data;
+ public RequestCallback callback;
+ public int mtu;
+ public int delay;
+ public Object tag;
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, RequestCallback callback) {
+ this(serviceUuid, characteristicUuid, type, null, 0, callback, 0);
+ }
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, RequestCallback callback, int delay) {
+ this(serviceUuid, characteristicUuid, type, null, 0, callback, delay);
+ }
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, byte[] data, RequestCallback callback) {
+ this(serviceUuid, characteristicUuid, type, data, 0, callback, 0);
+ }
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, byte[] data, RequestCallback callback, int delay) {
+ this(serviceUuid, characteristicUuid, type, data, 0, callback, delay);
+ }
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, int mtu, RequestCallback callback) {
+ this(serviceUuid, characteristicUuid, type, null, mtu, callback, 0);
+ }
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, int mtu, RequestCallback callback, int delay) {
+ this(serviceUuid, characteristicUuid, type, null, mtu, callback, delay);
+ }
+
+ public Request(UUID serviceUuid, UUID characteristicUuid, @RequestType int type, byte[] data, int mtu, RequestCallback callback, int delay) {
+ this.requestType = type;
+ this.serviceUuid = serviceUuid;
+ this.characteristicUuid = characteristicUuid;
+ this.data = data;
+ this.callback = callback;
+ this.mtu = mtu;
+ // 最小23个字节
+ // 最大517个字节
+ if (this.mtu < MTU_MIN) {
+ this.mtu = MTU_MIN;
+ } else if (this.mtu > MTU_MAX) {
+ this.mtu = MTU_MAX;
+ }
+ this.delay = delay;
+ }
+
+ public void setTag(Object tag) {
+ this.tag = tag;
+ }
+
+ public Object getTag() {
+ return this.tag;
+ }
+
+ public static Request newWriteRequest(UUID serviceUuid, UUID characteristicUuid, byte[] data, RequestCallback callback) {
+ return new Request(serviceUuid, characteristicUuid, WRITE, data, callback);
+ }
+
+ public static Request newWriteRequest(UUID serviceUuid, UUID characteristicUuid, byte[] data, RequestCallback callback, int delay) {
+ return new Request(serviceUuid, characteristicUuid, WRITE, data, callback, delay);
+ }
+
+ public static Request newReadRequest(UUID serviceUuid, UUID characteristicUuid, RequestCallback callback) {
+ return new Request(serviceUuid, characteristicUuid, READ, callback);
+ }
+
+ public static Request newReadRequest(UUID serviceUuid, UUID characteristicUuid, RequestCallback callback, int delay) {
+ return new Request(serviceUuid, characteristicUuid, READ, callback, delay);
+ }
+
+ public static Request newMtuRequest(UUID serviceUuid, UUID characteristicUuid, int mtu, RequestCallback callback) {
+ return new Request(serviceUuid, characteristicUuid, MTU, mtu, callback);
+ }
+
+ public static Request newMtuRequest(UUID serviceUuid, UUID characteristicUuid, int mtu, RequestCallback callback, int delay) {
+ return new Request(serviceUuid, characteristicUuid, MTU, mtu, callback, delay);
+ }
+
+ public static Request newEnableNotifyRequest(UUID serviceUuid, UUID characteristicUuid, RequestCallback callback) {
+ return new Request(serviceUuid, characteristicUuid, ENABLE_NOTIFY, callback);
+ }
+
+ public static Request newEnableNotifyRequest(UUID serviceUuid, UUID characteristicUuid, RequestCallback callback, int delay) {
+ return new Request(serviceUuid, characteristicUuid, ENABLE_NOTIFY, callback, delay);
+ }
+
+ public static Request newDisableNotifyRequest(UUID serviceUuid, UUID characteristicUuid, RequestCallback callback) {
+ return new Request(serviceUuid, characteristicUuid, DISABLE_NOTIFY, callback);
+ }
+
+ public static Request newDisableNotifyRequest(UUID serviceUuid, UUID characteristicUuid, RequestCallback callback, int delay) {
+ return new Request(serviceUuid, characteristicUuid, DISABLE_NOTIFY, callback, delay);
+ }
+
+}
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/util/Logger.java b/fastblegatt/src/main/java/com/cys/fastblegatt/util/Logger.java
new file mode 100644
index 0000000..f159a72
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/util/Logger.java
@@ -0,0 +1,17 @@
+package com.cys.fastblegatt.util;
+
+import android.util.Log;
+
+public class Logger {
+
+ public static boolean isDebug = false;
+ public static String MAIN_TAG = "FastBleGatt-Log";
+
+ public static void d(String message){
+ if (isDebug){
+ Log.d(MAIN_TAG, message);
+ }
+ }
+
+
+}
diff --git a/fastblegatt/src/main/java/com/cys/fastblegatt/util/PrintHelpper.java b/fastblegatt/src/main/java/com/cys/fastblegatt/util/PrintHelpper.java
new file mode 100644
index 0000000..c107b4c
--- /dev/null
+++ b/fastblegatt/src/main/java/com/cys/fastblegatt/util/PrintHelpper.java
@@ -0,0 +1,68 @@
+package com.cys.fastblegatt.util;
+
+import android.text.TextUtils;
+
+import java.util.Formatter;
+
+public class PrintHelpper {
+
+ /**
+ * 数组转十进制字符串
+ *
+ * @param array 数组
+ * @return 字符串
+ */
+ public static String bytesToString(byte[] array) {
+ if (array == null) {
+ return "null";
+ }
+ if (array.length == 0) {
+ return "[]";
+ }
+
+ StringBuffer sb = new StringBuffer(array.length * 6);
+ sb.append("[");
+ sb.append(array[0]);
+ for (int i = 1; i < array.length; i++) {
+ sb.append(" ");
+ sb.append(array[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * 数组转十六进制字符串
+ *
+ * @param array 数组
+ * @param separator 分隔符
+ * @return 字符串
+ */
+ public static String bytesToHexString(byte[] array, String separator) {
+
+ if (array == null) {
+ return "null";
+ }
+ if (array.length == 0) {
+ return "[]";
+ }
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ Formatter formatter = new Formatter(sb);
+ formatter.format("%02X", array[0]);
+ for (int i = 1; i < array.length; i++) {
+ if (!TextUtils.isEmpty(separator)) {
+ sb.append(separator);
+ }
+ formatter.format("%02X", array[i]);
+ }
+ sb.append("]");
+
+ formatter.flush();
+ formatter.close();
+
+ return sb.toString();
+ }
+
+}
diff --git a/fastblegatt/src/main/res/values/strings.xml b/fastblegatt/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d8b1ac9
--- /dev/null
+++ b/fastblegatt/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ lib
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..d546dea
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d06a657
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon May 13 18:59:34 GMT+08:00 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..6fb5750
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':fastblegatt'