zhuguifei
2025-12-30 61eee1173c00a7ba9d9c748d28fe3acdb33b9441
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
package com.blakequ.bluetooth_manager_lib.connect;
 
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
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.blakequ.bluetooth_manager_lib.util.BluetoothUtils;
import com.blankj.utilcode.util.LogUtils;
 
import java.lang.reflect.Field;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
 
/**
 * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com>
 * <p/>
 * Licensed under the blakequ.com License, Version 1.0 (the "License");
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * <p/>
 * author  : quhao <blakequ@gmail.com> <br>
 * date     : 2016/8/19 11:16 <br>
 * last modify author : <br>
 * version : 1.0 <br>
 * description:bluetooth connect interface
 */
public abstract class BluetoothConnectInterface {
    protected static final String TAG = "BluetoothConnectInterface";
    protected Context context;
    private BluetoothOperatorQueue mOpratorQueue;
    private Handler mHandler = new Handler(Looper.getMainLooper());
 
    public BluetoothConnectInterface(Context context){
        this.context = context;
        mOpratorQueue = new BluetoothOperatorQueue();
    }
 
    /**
     * release resource
     */
    public abstract void release();
 
    /**
     * get gatt connect callback
     * @return
     */
    protected abstract BluetoothGattCallback getBluetoothGattCallback();
 
    protected abstract String getServiceUUID();
 
    /**
     * get device gatt service, if not will return null
     * @param address
     * @return null if not find gatt service
     */
    public abstract BluetoothGatt getBluetoothGatt(String address);
 
    /**
     * get the list of subscribe
     * @return
     */
    protected abstract Queue<BluetoothSubScribeData> getSubscribeDataQueue();
 
    /**
     * invoke when bluetooth disconnect
     * @param gatt
     */
    protected abstract void onDeviceDisconnect(BluetoothGatt gatt, int errorState);
 
    /**
     * invoke when bluetooth connected
     * @param gatt
     */
    protected abstract void onDeviceConnected(BluetoothGatt gatt);
 
    /**
     * invoke when fail to discover service
     * @param gatt
     */
    protected abstract void onDiscoverServicesFail(BluetoothGatt gatt);
 
    /**
     * invoke when success to discover service
     * @param gatt
     */
    protected abstract void onDiscoverServicesSuccess(BluetoothGatt gatt);
 
    /**
     * Runs the specified action on the UI thread. If the current thread is the UI
     * thread, then the action is executed immediately. If the current thread is
     * not the UI thread, the action is posted to the event queue of the UI thread.
     *
     * @param action the action to run on the UI thread
     */
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
 
    protected Handler getMainLooperHandler(){
        return mHandler;
    }
 
    protected BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onCharacteristicChanged(gatt, characteristic);
        }
 
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            LogUtils.i("onCharacteristicRead data status:" + GattError.parseConnectionError(status) + " " + characteristic.getUuid().toString());
            mOpratorQueue.nextOperator();
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onCharacteristicRead(gatt, characteristic, status);
        }
 
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            LogUtils.i("onCharacteristicWrite write status:" + GattError.parseConnectionError(status));
            mOpratorQueue.nextOperator();
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onCharacteristicWrite(gatt, characteristic, status);
        }
 
        @Override
        public void onConnectionStateChange(final BluetoothGatt gatt, int status, final int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            //status=133是GATT_ERROR错误http://stackoverflow.com/questions/25330938/android-bluetoothgatt-status-133-register-callback
            //http://www.loverobots.cn/android-ble-connection-solution-bluetoothgatt-status-133.html
            LogUtils.i("onConnectionStateChange gattStatus=" + GattError.parseConnectionError(status) + " newStatus="
                    + (newState == BluetoothProfile.STATE_CONNECTED ? "CONNECTED" : "DISCONNECTED"));
 
            //不同的手机当蓝牙关闭,设备断开(重启,远离)返回的状态不一样,newState都一样是DISCONNECTED,设备切换不会产生影响
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {//调用connect会调用
                    LogUtils.i("Connected to GATT server");
                    // Attempts to discover services after successful connection.
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            onDeviceConnected(gatt);
                            if (gatt != null && !gatt.discoverServices()) {
                                LogUtils.e("onConnectionStateChange start service discovery fail! Thread:" + Thread.currentThread());
                            }
                        }
                    });
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//调用disconnect会调用,设备断开或蓝牙关闭会进入
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            onDeviceDisconnect(gatt, newState);
                        }
                    });
                }
            } else{ //调用connect和disconnect出错后会进入,设备断开或蓝牙关闭会进入
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        onDeviceDisconnect(gatt, newState);
                    }
                });
            }
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onConnectionStateChange(gatt, status, newState);
        }
 
        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
            LogUtils.i("onDescriptorRead status=" + GattError.parseConnectionError(status));
            mOpratorQueue.nextOperator();
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onDescriptorRead(gatt, descriptor, status);
        }
 
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            LogUtils.i("onDescriptorWrite status=" + GattError.parseConnectionError(status));
            mOpratorQueue.nextOperator();
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onDescriptorWrite(gatt, descriptor, status);
        }
 
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onMtuChanged(gatt, mtu, status);
        }
 
        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            super.onReadRemoteRssi(gatt, rssi, status);
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onReadRemoteRssi(gatt, rssi, status);
        }
 
        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            super.onReliableWriteCompleted(gatt, status);
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onReliableWriteCompleted(gatt, status);
        }
 
        @Override
        public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
            LogUtils.i("onServicesDiscovered status=" + GattError.parseConnectionError(status));
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //start subscribe data
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        onDiscoverServicesSuccess(gatt);
                        if (gatt != null){
                            startSubscribe(gatt);
                        }
                    }
                });
            }else {
                LogUtils.e("onServicesDiscovered fail!");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        onDiscoverServicesFail(gatt);
                    }
                });
            }
            if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onServicesDiscovered(gatt, status);
        }
    };
 
    /**
     * start subscribe data, add data to subscribe list before invoke this method
     * <p>You should invoke this method after onServicesDiscovered, otherwise can not find service<p/>
     * @param bluetoothGatt
     * @return boolean is success start read or write character
     */
    public boolean startSubscribe(BluetoothGatt bluetoothGatt){
        if (bluetoothGatt == null){
            LogUtils.e("Fail to subscribe, BluetoothGatt is null");
            return false;
        }
        boolean isSuccess = subscribe(bluetoothGatt.getDevice().getAddress());
        mOpratorQueue.start(bluetoothGatt);
        return isSuccess;
    }
 
    /**
     * 订阅蓝牙设备通知及读写数据
     * @return
     */
    protected boolean subscribe(String address){
        BluetoothGatt mBluetoothGatt = getBluetoothGatt(address);
        if (mBluetoothGatt == null){
            LogUtils.e("can not subscribe to ble device info "+address);
            return false;
        }
        mOpratorQueue.clean();
 
        if (isEmpty(getServiceUUID())){
            LogUtils.e("Service UUID is null");
            return false;
        }
 
        //check subscribe list
        if (getSubscribeDataQueue() == null && getSubscribeDataQueue().size() > 0){
            LogUtils.e("Subscribe BLE data is null, you must invoke addBluetoothSubscribeData to add data");
            return false;
        }
 
        BluetoothGattService gattService = mBluetoothGatt.getService(UUID.fromString(getServiceUUID()));
        if (gattService != null){
            for (BluetoothSubScribeData data:getSubscribeDataQueue()){
                final BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(data.getCharacteristicUUID());
                if (characteristic != null){
                    switch (data.getOperatorType()){
                        case CHAR_WIRTE:
                            if (BluetoothUtils.isCharacteristicWrite(characteristic.getProperties())){
                                characteristic.setValue(data.getCharacteristicValue());
                                mOpratorQueue.addOperator(characteristic, true);
                            }else{
                                LogUtils.e("Fail to write characteristic, not have write property , uuid:"+characteristic.getUuid()+" ,property:"+characteristic.getProperties());
                            }
                            break;
                        case CHAR_READ:
                            //bug fix:samsung phone bug, can not read value
                            if (checkIsSamsung()){
                                setProperty(characteristic);
                            }
                            if(BluetoothUtils.isCharacteristicRead(characteristic.getProperties())){
                                mOpratorQueue.addOperator(characteristic, false);
                            }else{
                                LogUtils.e("Fail to read characteristic, not have read property , uuid:" + characteristic.getUuid() + " ,property:" + characteristic.getProperties());
                            }
                            break;
                        case DESC_READ:
                            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(data.getDescriptorUUID());
                            if (descriptor != null){
                                mOpratorQueue.addOperator(descriptor, false);
                            }else {
                                LogUtils.e("Fail to get descriptor read uuid:"+data.getDescriptorUUID());
                            }
                            break;
                        case DESC_WRITE:
                            BluetoothGattDescriptor descriptor2 = characteristic.getDescriptor(data.getDescriptorUUID());
                            if (descriptor2 != null){
                                descriptor2.setValue(data.getDescriptorValue());
                                mOpratorQueue.addOperator(descriptor2, true);
                            }else {
                                LogUtils.e("Fail to get descriptor write uuid:"+data.getDescriptorUUID());
                            }
                            break;
                        case NOTIFY:
                            if(BluetoothUtils.isCharacteristicNotify(characteristic.getProperties())){
                                mBluetoothGatt.setCharacteristicNotification(characteristic, true);
                                BluetoothGattDescriptor descriptor3 = characteristic.getDescriptor(data.getDescriptorUUID());
                                if (descriptor3 != null){
                                    if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0){
                                        descriptor3.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                                    }else if((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0){
                                        //两个都是通知的意思,notify和indication的区别在于,notify只是将你要发的数据发送给手机,没有确认机制,
                                        //不会保证数据发送是否到达。而indication的方式在手机收到数据时会主动回一个ack回来。即有确认机制,只有收
                                        //到这个ack你才能继续发送下一个数据。这保证了数据的正确到达,也起到了流控的作用。所以在打开通知的时候,需要设置一下。
                                        descriptor3.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                                    }
                                    mOpratorQueue.addOperator(descriptor3, true);
                                }else {
                                    LogUtils.e("Fail to get notify descriptor uuid:"+data.getDescriptorUUID());
                                }
                            }else{
                                LogUtils.e("Fail to notify characteristic, not have notify property , uuid:" + characteristic.getUuid() + " ,property:" + characteristic.getProperties());
                            }
                            break;
                    }
                }else {
                    LogUtils.e("Fail to get characteristic service uuid:"+data.getCharacteristicUUID());
                }
            }
        }else {
            LogUtils.e("Can not get gatt service uuid:"+getServiceUUID());
            return false;
        }
        return true;
    }
 
    /**
     * 设置属性,设置读权限
     * @param flagReadChar
     */
    @SuppressLint("SoonBlockedPrivateApi")
    private void setProperty(BluetoothGattCharacteristic flagReadChar){
        Field properField = null;
        try {
            properField = flagReadChar.getClass().getDeclaredField("mProperties");
            properField.setAccessible(true);
            properField.set(flagReadChar, 10);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 判断手机类型
     * @return
     */
    protected boolean checkIsSamsung() {
        String brand = android.os.Build.BRAND;
        if (brand.toLowerCase().equals("samsung")) {
            return true;
        }
        return false;
    }
 
    public boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }
 
    public <V> boolean isEmpty(List<V> sourceList) {
        return sourceList == null || sourceList.size() == 0;
    }
 
}