parent
							
								
									eb98dc0826
								
							
						
					
					
						commit
						aaf76a2ab7
					
				@ -0,0 +1 @@
 | 
			
		||||
/build
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'com.android.library'
 | 
			
		||||
    id 'kotlin-android'
 | 
			
		||||
    id 'kotlin-parcelize'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdkVersion rootProject.ext.android.compileSdkVersion
 | 
			
		||||
    buildToolsVersion rootProject.ext.android.buildToolsVersion
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        minSdkVersion rootProject.ext.android.minSdkVersion
 | 
			
		||||
        targetSdkVersion rootProject.ext.android.targetSdkVersion
 | 
			
		||||
        versionCode rootProject.ext.android.versionCode
 | 
			
		||||
        versionName rootProject.ext.android.versionName
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    viewBinding {
 | 
			
		||||
        enabled = true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(path: ':commonLib')
 | 
			
		||||
    implementation 'androidx.appcompat:appcompat:1.3.1'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.4.0'
 | 
			
		||||
 | 
			
		||||
    // 添加kotlin依赖
 | 
			
		||||
    implementation rootProject.ext.dependencies.kotlin
 | 
			
		||||
}
 | 
			
		||||
@ -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
 | 
			
		||||
@ -0,0 +1,39 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="com.common.bluetooth">
 | 
			
		||||
 | 
			
		||||
    <!-- 检测蓝牙状态 -->
 | 
			
		||||
    <uses-feature
 | 
			
		||||
        android:name="android.hardware.bluetooth_le"
 | 
			
		||||
        android:required="true" />
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 | 
			
		||||
    <!-- 增加蓝牙所需要的权限 -->
 | 
			
		||||
    <uses-permission android:name="android.permission.BLUETOOTH" />
 | 
			
		||||
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 | 
			
		||||
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
 | 
			
		||||
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.VIBRATE" />
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true">
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="com.common.bluetooth.BtDemoActivity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:screenOrientation="portrait"
 | 
			
		||||
            android:theme="@style/Theme.AppCompat"/>
 | 
			
		||||
        <service
 | 
			
		||||
            android:name="com.common.bluetooth.service.BLEReceiveService"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:enabled="true">
 | 
			
		||||
        </service>
 | 
			
		||||
 | 
			
		||||
        <service
 | 
			
		||||
            android:name="com.common.bluetooth.service.BLEClientService"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:enabled="true">
 | 
			
		||||
        </service>
 | 
			
		||||
    </application>
 | 
			
		||||
</manifest>
 | 
			
		||||
@ -0,0 +1,99 @@
 | 
			
		||||
package com.common.bluetooth
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.provider.Settings
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * BT静态方法
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-14
 | 
			
		||||
 */
 | 
			
		||||
object BtConstants {
 | 
			
		||||
    const val BT_NAME = "innovation bt"
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 蓝牙类型
 | 
			
		||||
     */
 | 
			
		||||
    enum class BLUETOOTH_TYPE {
 | 
			
		||||
        /**
 | 
			
		||||
         * 经典蓝牙
 | 
			
		||||
         */
 | 
			
		||||
        CLASSIC,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 低功耗蓝牙
 | 
			
		||||
         */
 | 
			
		||||
        BLE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 蓝牙类型
 | 
			
		||||
     */
 | 
			
		||||
    enum class CLIENT_TYPE {
 | 
			
		||||
        /**
 | 
			
		||||
         * 服务端
 | 
			
		||||
         */
 | 
			
		||||
        SERVER,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 客户端
 | 
			
		||||
         */
 | 
			
		||||
        CLIENT
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private const val STATUS_BASE = 0x1
 | 
			
		||||
    const val CONNECT_SUCCESS = STATUS_BASE shl 1
 | 
			
		||||
    const val CONNECT_ERROR = STATUS_BASE shl 2
 | 
			
		||||
    const val READ_SUCCESS = STATUS_BASE shl 3
 | 
			
		||||
    const val READ_ERROR = STATUS_BASE shl 4
 | 
			
		||||
    const val WRITE_SUCCESS = STATUS_BASE shl 5
 | 
			
		||||
    const val WRITE_ERROR = STATUS_BASE shl 6
 | 
			
		||||
    const val CREATE_SERVER_SUCCESS = STATUS_BASE shl 7
 | 
			
		||||
    const val CREATE_SERVER_ERROR = STATUS_BASE shl 8
 | 
			
		||||
    const val DISCONNECT = STATUS_BASE shl 9
 | 
			
		||||
 | 
			
		||||
    enum class EXCEPTION(s: String) {
 | 
			
		||||
        NOT_CONNECTED("not connect"),
 | 
			
		||||
        NULL_SERVICE("service is null"),
 | 
			
		||||
        NULL_CHARACTERISTIC("characteristic is null"),
 | 
			
		||||
        READ_EXCEPTION("characteristic read error"),
 | 
			
		||||
        WRITE_EXCEPTION("characteristic write error"),
 | 
			
		||||
        NOTIFY_OPEN_EXCEPTION("notification open error"),
 | 
			
		||||
        NOTIFY_CLOSE_EXCEPTION("notification close error")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Message types sent from the BluetoothChatService Handler
 | 
			
		||||
    const val MESSAGE_STATE_CHANGE = 1
 | 
			
		||||
    const val MESSAGE_READ = 2
 | 
			
		||||
    const val MESSAGE_WRITE = 3
 | 
			
		||||
    const val MESSAGE_DEVICE_NAME = 4
 | 
			
		||||
    const val MESSAGE_TOAST = 5
 | 
			
		||||
 | 
			
		||||
    // Key names received from the BluetoothChatService Handler
 | 
			
		||||
    const val DEVICE_NAME = "device_name"
 | 
			
		||||
    const val TOAST = "toast"
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 最大的单包发送字节数
 | 
			
		||||
     */
 | 
			
		||||
    const val MAX_MTU = 35
 | 
			
		||||
 | 
			
		||||
    val UUID_SERVICE = UUID.fromString("66f564dc-121f-3e7f-80b1-f005d3f194c9")
 | 
			
		||||
    val UUID_CHARACTERISTIC_NOTIFY = UUID.fromString("66f564dd-121f-3e7f-80b1-f005d3f194c9")
 | 
			
		||||
    val UUID_CHARACTERISTIC_WRITE = UUID.fromString("66f564de-121f-3e7f-80b1-f005d3f194c9")
 | 
			
		||||
    val UUID_DESCRIPTOR = UUID.fromString("66f564df-121f-3e7f-80b1-f005d3f194c9")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取UUID
 | 
			
		||||
     */
 | 
			
		||||
    fun getUUid(context: Context?): UUID {
 | 
			
		||||
        if (context == null) {
 | 
			
		||||
            return UUID.randomUUID()
 | 
			
		||||
        }
 | 
			
		||||
        val androidId =
 | 
			
		||||
            Settings.System.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
 | 
			
		||||
        return UUID.nameUUIDFromBytes(androidId.toByteArray())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,191 @@
 | 
			
		||||
package com.common.bluetooth;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity;
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager;
 | 
			
		||||
 | 
			
		||||
import android.bluetooth.BluetoothAdapter;
 | 
			
		||||
import android.bluetooth.BluetoothDevice;
 | 
			
		||||
import android.content.BroadcastReceiver;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.IntentFilter;
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.bean.AssociateEvent;
 | 
			
		||||
import com.common.bluetooth.bean.AssociateSourceEvent;
 | 
			
		||||
import com.common.bluetooth.bean.CommonMsg;
 | 
			
		||||
import com.common.bluetooth.bean.KeyboardEvent;
 | 
			
		||||
import com.common.bluetooth.callback.BLEClientListener;
 | 
			
		||||
import com.common.bluetooth.databinding.ActivityMainBinding;
 | 
			
		||||
import com.common.bluetooth.view.BtDeviceListAdapter;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙样例代码
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-2
 | 
			
		||||
 */
 | 
			
		||||
public class BtDemoActivity extends AppCompatActivity {
 | 
			
		||||
    private static final String TAG = "BtMainActivity";
 | 
			
		||||
    /**
 | 
			
		||||
     * 启动蓝牙请求码
 | 
			
		||||
     */
 | 
			
		||||
    private static final int REQUEST_ENABLE_BT = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取位置权限
 | 
			
		||||
     */
 | 
			
		||||
    private static final int PERMISSION_REQUEST_LOCATION = 1;
 | 
			
		||||
 | 
			
		||||
    private ActivityMainBinding mBinding = null;
 | 
			
		||||
    private BtDeviceListAdapter btDeviceListAdapter = null;
 | 
			
		||||
    private final ArrayList<BluetoothDevice> bluetoothDevices = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        mBinding = ActivityMainBinding.inflate(getLayoutInflater());
 | 
			
		||||
        setContentView(mBinding.getRoot());
 | 
			
		||||
 | 
			
		||||
        initView();
 | 
			
		||||
        checkBluetooth();
 | 
			
		||||
        initData();
 | 
			
		||||
 | 
			
		||||
        // 设置广播信息过滤
 | 
			
		||||
        IntentFilter filter = new IntentFilter();
 | 
			
		||||
        filter.addAction(BluetoothDevice.ACTION_FOUND);//每搜索到一个设备就会发送一个该广播
 | 
			
		||||
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//当全部搜索完后发送该广播
 | 
			
		||||
        filter.setPriority(Integer.MAX_VALUE);//设置优先级
 | 
			
		||||
        // 注册蓝牙搜索广播接收者,接收并处理搜索结果
 | 
			
		||||
        this.registerReceiver(receiver, filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initData() {
 | 
			
		||||
        bluetoothDevices.clear();
 | 
			
		||||
        Set<BluetoothDevice> bondedDevices = BtManager.INSTANCE.getBoundDevices();
 | 
			
		||||
        bluetoothDevices.addAll(bondedDevices);
 | 
			
		||||
 | 
			
		||||
        btDeviceListAdapter = new BtDeviceListAdapter(this, bluetoothDevices);
 | 
			
		||||
        LinearLayoutManager manager = new LinearLayoutManager(getApplicationContext());
 | 
			
		||||
        mBinding.bleRv.setLayoutManager(manager);
 | 
			
		||||
        mBinding.bleRv.setAdapter(btDeviceListAdapter);
 | 
			
		||||
 | 
			
		||||
        btDeviceListAdapter.setOnDeviceClickListener(
 | 
			
		||||
                index ->
 | 
			
		||||
                {
 | 
			
		||||
                    BtManager.INSTANCE.connect(bluetoothDevices.get(index).getAddress());
 | 
			
		||||
                    BtManager.INSTANCE.setClientListener(new BLEClientListener() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void onResult(@NonNull CommonMsg result) {
 | 
			
		||||
                            Log.e("wangym", "onResult = " + result.getMsg());
 | 
			
		||||
                            runOnUiThread(
 | 
			
		||||
                                    () -> Toast.makeText(BtDemoActivity.this, result.getMsg(),
 | 
			
		||||
                                            Toast.LENGTH_SHORT).show());
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void onNotifyMsgReceive(@NonNull byte[] msg) {
 | 
			
		||||
                            Log.e("wangym", "notify = " + new String(msg));
 | 
			
		||||
                            runOnUiThread(
 | 
			
		||||
                                    () -> Toast.makeText(BtDemoActivity.this, new String(msg),
 | 
			
		||||
                                            Toast.LENGTH_SHORT).show());
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initView() {
 | 
			
		||||
        mBinding.searchBle.setOnClickListener(
 | 
			
		||||
                l -> BtManager.INSTANCE.searchBtDevice(BtConstants.BLUETOOTH_TYPE.CLASSIC));
 | 
			
		||||
 | 
			
		||||
        mBinding.sendMsg.setOnClickListener(l -> {
 | 
			
		||||
            String value = "*" + mBinding.msgEt.getText().toString() + "#";
 | 
			
		||||
            BtManager.INSTANCE.writeMsg(value);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        mBinding.buildBleServer.setOnClickListener(v -> {
 | 
			
		||||
            BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.BLE);
 | 
			
		||||
            BtManager.INSTANCE.setMsgReceiverListener(msg -> runOnUiThread(() -> {
 | 
			
		||||
                Object event = BtUtils.INSTANCE.getEventFromSendMsg(msg);
 | 
			
		||||
                if (event == null) {
 | 
			
		||||
                    mBinding.receiveContent.setText(new String(msg));
 | 
			
		||||
                } else if (event instanceof KeyboardEvent) {
 | 
			
		||||
                    KeyboardEvent temp = (KeyboardEvent) event;
 | 
			
		||||
                    mBinding.receiveContent.setText(new Gson().toJson(temp));
 | 
			
		||||
                } else if (event instanceof AssociateEvent) {
 | 
			
		||||
                    AssociateEvent temp = (AssociateEvent) event;
 | 
			
		||||
                    mBinding.receiveContent.setText(new Gson().toJson(temp));
 | 
			
		||||
                } else if (event instanceof AssociateSourceEvent) {
 | 
			
		||||
                    AssociateSourceEvent temp = (AssociateSourceEvent) event;
 | 
			
		||||
                    mBinding.receiveContent.setText(new Gson().toJson(temp));
 | 
			
		||||
                }
 | 
			
		||||
            }));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkBluetooth() {
 | 
			
		||||
        BtManager.INSTANCE.initClient(this, BtConstants.BLUETOOTH_TYPE.BLE);
 | 
			
		||||
        BtManager.INSTANCE.btPermissionCheck(this, PERMISSION_REQUEST_LOCATION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 蓝牙扫描事件接收
 | 
			
		||||
     */
 | 
			
		||||
    private final BroadcastReceiver receiver = new BroadcastReceiver() {
 | 
			
		||||
        public void onReceive(Context context, Intent intent) {
 | 
			
		||||
            String action = intent.getAction();
 | 
			
		||||
            Log.d(TAG, "action = " + action);
 | 
			
		||||
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
 | 
			
		||||
                // Discovery has found a device. Get the BluetoothDevice
 | 
			
		||||
                // object and its info from the Intent.
 | 
			
		||||
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 | 
			
		||||
                bluetoothDevices.add(device);
 | 
			
		||||
                btDeviceListAdapter.notifyDataSetChanged();
 | 
			
		||||
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {//说明搜索已经完成
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
 | 
			
		||||
        super.onActivityResult(requestCode, resultCode, data);
 | 
			
		||||
        if (requestCode == REQUEST_ENABLE_BT) {
 | 
			
		||||
            if (resultCode == RESULT_OK) {
 | 
			
		||||
                Toast.makeText(this, "蓝牙打开成功", Toast.LENGTH_SHORT).show();
 | 
			
		||||
            } else {
 | 
			
		||||
                Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_SHORT).show();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
 | 
			
		||||
                                           @NonNull int[] grantResults) {
 | 
			
		||||
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 | 
			
		||||
        switch (requestCode) {
 | 
			
		||||
            case PERMISSION_REQUEST_LOCATION:
 | 
			
		||||
                if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                    finish();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDestroy() {
 | 
			
		||||
        super.onDestroy();
 | 
			
		||||
        BtManager.INSTANCE.release(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
package com.common.bluetooth.bean
 | 
			
		||||
 | 
			
		||||
import android.os.Parcelable
 | 
			
		||||
import kotlinx.parcelize.Parcelize
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 联想词
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-1
 | 
			
		||||
 */
 | 
			
		||||
@Parcelize
 | 
			
		||||
data class AssociateItem(
 | 
			
		||||
    /**
 | 
			
		||||
     * 联想词
 | 
			
		||||
     */
 | 
			
		||||
    var name: String = "",
 | 
			
		||||
    /**
 | 
			
		||||
     * 序列号
 | 
			
		||||
     */
 | 
			
		||||
    var index: Int = 0
 | 
			
		||||
) : Parcelable
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
package com.common.bluetooth.bean
 | 
			
		||||
 | 
			
		||||
import android.os.Parcelable
 | 
			
		||||
import kotlinx.parcelize.Parcelize
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 待联想词事件
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-1
 | 
			
		||||
 */
 | 
			
		||||
@Parcelize
 | 
			
		||||
data class AssociateSourceEvent(
 | 
			
		||||
    /**
 | 
			
		||||
     * 待联想词
 | 
			
		||||
     */
 | 
			
		||||
    var associateSource: String = ""
 | 
			
		||||
) : Parcelable
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
package com.common.bluetooth.bean
 | 
			
		||||
 | 
			
		||||
import android.os.Parcelable
 | 
			
		||||
import kotlinx.parcelize.Parcelize
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 基础事件类型
 | 
			
		||||
 */
 | 
			
		||||
@Parcelize
 | 
			
		||||
data class BaseEvent(
 | 
			
		||||
    /**
 | 
			
		||||
     * 事件类型
 | 
			
		||||
     */
 | 
			
		||||
    var type: String = "",
 | 
			
		||||
    /**
 | 
			
		||||
     * 事件内容
 | 
			
		||||
     */
 | 
			
		||||
    var data: String = ""
 | 
			
		||||
) : Parcelable
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
package com.common.bluetooth.bean
 | 
			
		||||
 | 
			
		||||
import android.os.Parcelable
 | 
			
		||||
import kotlinx.parcelize.Parcelize
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 各类消息
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-1
 | 
			
		||||
 */
 | 
			
		||||
@Parcelize
 | 
			
		||||
data class CommonMsg(
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息类型
 | 
			
		||||
     */
 | 
			
		||||
    var msgType: Int,
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息内容
 | 
			
		||||
     */
 | 
			
		||||
    var msg: String
 | 
			
		||||
) : Parcelable
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
package com.common.bluetooth.callback
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.bean.CommonMsg
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 客户端监听
 | 
			
		||||
 */
 | 
			
		||||
interface BLEClientListener {
 | 
			
		||||
    /**
 | 
			
		||||
     * 结果
 | 
			
		||||
     * @param result 回调
 | 
			
		||||
     */
 | 
			
		||||
    fun onResult(result: CommonMsg)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 接收服务端通知
 | 
			
		||||
     */
 | 
			
		||||
    fun onNotifyMsgReceive(msg: ByteArray)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
package com.common.bluetooth.callback;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用于数据回传的基本回调接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public interface BaseResultCallback<T> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 成功拿到数据
 | 
			
		||||
     *
 | 
			
		||||
     * @param data 回传的数据
 | 
			
		||||
     */
 | 
			
		||||
    void onSuccess(T data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 操作失败
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg 失败的返回的异常信息
 | 
			
		||||
     */
 | 
			
		||||
    void onFail(String msg);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.common.bluetooth.callback
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * BLE数据接收监听
 | 
			
		||||
 */
 | 
			
		||||
interface BleMsgReceiverListener {
 | 
			
		||||
    /**
 | 
			
		||||
     * 接收的消息
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg 传输的数据
 | 
			
		||||
     */
 | 
			
		||||
    fun onMsgReceive(msg: ByteArray)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
package com.common.bluetooth.callback
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙链接监听
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-11
 | 
			
		||||
 */
 | 
			
		||||
interface BtConnectListener {
 | 
			
		||||
    /**
 | 
			
		||||
     * 链接成功
 | 
			
		||||
     */
 | 
			
		||||
    fun onSuccess()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 链接失败
 | 
			
		||||
     *
 | 
			
		||||
     * @param error 错误信息
 | 
			
		||||
     */
 | 
			
		||||
    fun onFail(error: String)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
package com.common.bluetooth.callback
 | 
			
		||||
 | 
			
		||||
import android.bluetooth.le.ScanCallback
 | 
			
		||||
import android.bluetooth.le.ScanResult
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 扫描结果回调
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-9
 | 
			
		||||
 */
 | 
			
		||||
open class BtScanCallBack : ScanCallback() {
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索结束
 | 
			
		||||
     */
 | 
			
		||||
    open fun onComplete() {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索报错
 | 
			
		||||
     * @param msg 错误信息
 | 
			
		||||
     */
 | 
			
		||||
    open fun onError(msg: String?) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索结果
 | 
			
		||||
     * @param result 搜索结果
 | 
			
		||||
     */
 | 
			
		||||
    open fun onResult(result: ScanResult?) {}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.common.bluetooth.exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙基础异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothException extends Exception {
 | 
			
		||||
    public BluetoothException(String msg) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.common.bluetooth.exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙未开启异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothNotOpenException extends BluetoothException {
 | 
			
		||||
    public BluetoothNotOpenException(String msg) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.common.bluetooth.exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙读异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothReadException extends BluetoothException {
 | 
			
		||||
    public BluetoothReadException(String msg) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.common.bluetooth.exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙搜索冲突异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothSearchConflictException extends BluetoothException {
 | 
			
		||||
    public BluetoothSearchConflictException(String msg) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
package com.common.bluetooth.exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙写异常
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothWriteException extends BluetoothException {
 | 
			
		||||
    private String mac = "";
 | 
			
		||||
 | 
			
		||||
    public BluetoothWriteException(String msg) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BluetoothWriteException(String msg, String mac) {
 | 
			
		||||
        super(msg);
 | 
			
		||||
        this.mac = mac;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
package com.common.bluetooth.interfaces
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙连接接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-9
 | 
			
		||||
 */
 | 
			
		||||
interface IBluetoothConnect {
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
package com.common.bluetooth.interfaces
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.callback.BtScanCallBack
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙搜索接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
interface IBluetoothSearch {
 | 
			
		||||
    /**
 | 
			
		||||
     * 启动扫描
 | 
			
		||||
     *
 | 
			
		||||
     * 指定开始扫描蓝牙服务. 如果一个扫描服务正在运行,
 | 
			
		||||
     * 马上停止当前的扫描服务, 只进行新的扫描服务.
 | 
			
		||||
     */
 | 
			
		||||
    fun startScan(delayTime: Long, callBack: BtScanCallBack?)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止扫描
 | 
			
		||||
     */
 | 
			
		||||
    fun stopScan()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否正在扫描
 | 
			
		||||
     *
 | 
			
		||||
     * @return 是否启动扫描成功
 | 
			
		||||
     */
 | 
			
		||||
    fun isScanning(): Boolean
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,320 @@
 | 
			
		||||
package com.common.bluetooth.service;
 | 
			
		||||
 | 
			
		||||
import static com.common.bluetooth.BtConstants.CONNECT_SUCCESS;
 | 
			
		||||
 | 
			
		||||
import android.app.Service;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.os.Binder;
 | 
			
		||||
import android.os.IBinder;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.BtConstants;
 | 
			
		||||
import com.common.bluetooth.BtUtils;
 | 
			
		||||
import com.common.bluetooth.adapter.BluetoothClientBLEAdapter;
 | 
			
		||||
import com.common.bluetooth.bean.CommonMsg;
 | 
			
		||||
import com.common.bluetooth.callback.BLEClientListener;
 | 
			
		||||
import com.common.bluetooth.callback.BaseResultCallback;
 | 
			
		||||
import com.common.bluetooth.callback.BtConnectListener;
 | 
			
		||||
import com.common.bluetooth.interfaces.IBluetoothClient;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.LinkedBlockingQueue;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
import io.reactivex.Observable;
 | 
			
		||||
import io.reactivex.Observer;
 | 
			
		||||
import io.reactivex.disposables.Disposable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * BLE客户端服务
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-29
 | 
			
		||||
 */
 | 
			
		||||
public class BLEClientService extends Service {
 | 
			
		||||
    private static final String TAG = "BLEClientService";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 空闲
 | 
			
		||||
     */
 | 
			
		||||
    private static final int WRITE_STATUS_IDLE = 1;
 | 
			
		||||
    /**
 | 
			
		||||
     * 传输中
 | 
			
		||||
     */
 | 
			
		||||
    private static final int WRITE_STATUS_WRITING = 2;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * BLE客户端
 | 
			
		||||
     */
 | 
			
		||||
    private IBluetoothClient mBtClient;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 连接的MAC
 | 
			
		||||
     */
 | 
			
		||||
    private String connectMac;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端事件监听器
 | 
			
		||||
     */
 | 
			
		||||
    private BLEClientListener clientListener;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息队列
 | 
			
		||||
     */
 | 
			
		||||
    private final LinkedBlockingQueue<byte[]> msgQueue = new LinkedBlockingQueue<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否开启通知
 | 
			
		||||
     */
 | 
			
		||||
    private final AtomicBoolean isEnableNotify = new AtomicBoolean(true);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 传输状态
 | 
			
		||||
     */
 | 
			
		||||
    private final AtomicInteger writeStatus = new AtomicInteger(WRITE_STATUS_IDLE);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreate() {
 | 
			
		||||
        super.onCreate();
 | 
			
		||||
 | 
			
		||||
        mBtClient = new BluetoothClientBLEAdapter(BluetoothLeClient.getInstance(this));
 | 
			
		||||
        mBtClient.checkBluetoothDevice(BtConstants.BLUETOOTH_TYPE.BLE);
 | 
			
		||||
        mBtClient.openBluetooth();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 连接服务端设备
 | 
			
		||||
     *
 | 
			
		||||
     * @param mac          MAC地址
 | 
			
		||||
     * @param enableNotify 是否注册通知
 | 
			
		||||
     */
 | 
			
		||||
    public void connect(String mac, boolean enableNotify) {
 | 
			
		||||
        connect(mac, enableNotify, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通知回调
 | 
			
		||||
     */
 | 
			
		||||
    private final BaseResultCallback<byte[]> notifyCallback = new BaseResultCallback<byte[]>() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onSuccess(byte[] data) {
 | 
			
		||||
            Log.d(TAG, "got notify onSuccess = " + new String(data));
 | 
			
		||||
            if (clientListener != null) {
 | 
			
		||||
                clientListener.onNotifyMsgReceive(data);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onFail(String msg) {
 | 
			
		||||
            Log.d(TAG, "got notify onFail = " + msg);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 连接服务端设备
 | 
			
		||||
     *
 | 
			
		||||
     * @param mac             MAC地址
 | 
			
		||||
     * @param enableNotify    是否注册通知
 | 
			
		||||
     * @param connectListener 链接回调
 | 
			
		||||
     */
 | 
			
		||||
    public void connect(String mac, boolean enableNotify, BtConnectListener connectListener) {
 | 
			
		||||
        this.connectMac = mac;
 | 
			
		||||
        isEnableNotify.set(enableNotify);
 | 
			
		||||
        if (TextUtils.isEmpty(connectMac)) {
 | 
			
		||||
            connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this);
 | 
			
		||||
        }
 | 
			
		||||
        Observable<String>[] observables = new Observable[2];
 | 
			
		||||
        observables[0] = mBtClient.connect(connectMac);
 | 
			
		||||
        // 判断是否需要启动通知
 | 
			
		||||
        if (enableNotify) {
 | 
			
		||||
            observables[1] = mBtClient.registerNotify(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(),
 | 
			
		||||
                    BtConstants.INSTANCE.getUUID_CHARACTERISTIC_NOTIFY(), notifyCallback);
 | 
			
		||||
        }
 | 
			
		||||
        Observable.concatArray(observables).subscribe(new Observer<String>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onSubscribe(@NonNull Disposable d) {
 | 
			
		||||
                Log.d(TAG, "connect onSubscribe");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onNext(@NonNull String value) {
 | 
			
		||||
                Log.d(TAG, String.format("connect onNext: %s", value));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onError(@NonNull Throwable e) {
 | 
			
		||||
                Log.e(TAG, "connect onError: ", e);
 | 
			
		||||
                // 链接过程报错,断开链接
 | 
			
		||||
                mBtClient.disconnect(connectMac);
 | 
			
		||||
                connectMac = "";
 | 
			
		||||
 | 
			
		||||
                if (clientListener != null) {
 | 
			
		||||
                    clientListener.onResult(new CommonMsg(BtConstants.CONNECT_ERROR, e.getMessage()));
 | 
			
		||||
                }
 | 
			
		||||
                if (connectListener != null) {
 | 
			
		||||
                    connectListener.onFail(e.getMessage());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onComplete() {
 | 
			
		||||
                Log.d(TAG, "connect onComplete");
 | 
			
		||||
                // 将链接成功的MAC地址保存
 | 
			
		||||
                BtUtils.INSTANCE.saveConnectMac(BLEClientService.this, mac);
 | 
			
		||||
                if (clientListener != null) {
 | 
			
		||||
                    clientListener.onResult(new CommonMsg(CONNECT_SUCCESS, mac));
 | 
			
		||||
                }
 | 
			
		||||
                if (connectListener != null) {
 | 
			
		||||
                    connectListener.onSuccess();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 传输内容
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg 内容
 | 
			
		||||
     */
 | 
			
		||||
    public void write(@NonNull final byte[] msg) {
 | 
			
		||||
        if (mBtClient == null) {
 | 
			
		||||
            Log.e(TAG, "write msg mBtClient got null");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(connectMac)) {
 | 
			
		||||
            connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this);
 | 
			
		||||
            if (TextUtils.isEmpty(connectMac)) {
 | 
			
		||||
                Log.e(TAG, "write msg connectMac got null");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Log.d(TAG, "add msg to queue : " + new String(msg));
 | 
			
		||||
 | 
			
		||||
        // 将字符串添加到队列中
 | 
			
		||||
        msgQueue.add(msg);
 | 
			
		||||
        doTranslate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 进行数据传输
 | 
			
		||||
     */
 | 
			
		||||
    private void doTranslate() {
 | 
			
		||||
        // 判断状态
 | 
			
		||||
        if (writeStatus.get() == WRITE_STATUS_WRITING) {
 | 
			
		||||
            Log.d(TAG, "doTranslate is writing, return");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        synchronized (BLEClientService.class) {
 | 
			
		||||
            writeStatus.set(WRITE_STATUS_WRITING);
 | 
			
		||||
            // 从队列中获取数据
 | 
			
		||||
            final byte[] msg = msgQueue.poll();
 | 
			
		||||
            if (msg == null) {
 | 
			
		||||
                Log.d(TAG, "doTranslate Queue is null");
 | 
			
		||||
                writeStatus.set(WRITE_STATUS_IDLE);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            Log.d(TAG, "doTranslate start translate : " + new String(msg));
 | 
			
		||||
            // 检查需要发送的内容是否超过了限额
 | 
			
		||||
            int size = msg.length / BtConstants.MAX_MTU;
 | 
			
		||||
            if (msg.length % BtConstants.MAX_MTU != 0) {
 | 
			
		||||
                size++;
 | 
			
		||||
            }
 | 
			
		||||
            Observable<String>[] observables = new Observable[size];
 | 
			
		||||
            for (int index = 0; index < size; index++) {
 | 
			
		||||
                byte[] temp;
 | 
			
		||||
                int arrSize;
 | 
			
		||||
                if (index == size - 1) {
 | 
			
		||||
                    arrSize = msg.length - (index * BtConstants.MAX_MTU);
 | 
			
		||||
                } else {
 | 
			
		||||
                    arrSize = BtConstants.MAX_MTU;
 | 
			
		||||
                }
 | 
			
		||||
                temp = new byte[arrSize];
 | 
			
		||||
                System.arraycopy(msg, index * BtConstants.MAX_MTU, temp, 0, arrSize);
 | 
			
		||||
                Observable<String> observable = mBtClient.write(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(),
 | 
			
		||||
                        BtConstants.INSTANCE.getUUID_CHARACTERISTIC_WRITE(),
 | 
			
		||||
                        temp);
 | 
			
		||||
                observables[index] = observable;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Observable.concatArray(observables).subscribe(new Observer<String>() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onSubscribe(@NonNull Disposable d) {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onNext(@NonNull String s) {
 | 
			
		||||
                    Log.d(TAG, String.format("write onNext %s", s));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onError(@NonNull Throwable e) {
 | 
			
		||||
                    Log.e(TAG, "write onError: " + e.getMessage());
 | 
			
		||||
                    if (BtConstants.EXCEPTION.NOT_CONNECTED.name().equals(e.getMessage())) {
 | 
			
		||||
                        // 写时发现,链接断开,则尝试重连
 | 
			
		||||
                        connect(connectMac, isEnableNotify.get(), new BtConnectListener() {
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void onSuccess() {
 | 
			
		||||
                                // 将数据重新放入队列后再次传输
 | 
			
		||||
                                msgQueue.add(msg);
 | 
			
		||||
 | 
			
		||||
                                writeStatus.set(WRITE_STATUS_IDLE);
 | 
			
		||||
                                doTranslate();
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void onFail(@NonNull String error) {
 | 
			
		||||
                                if (clientListener != null) {
 | 
			
		||||
                                    clientListener.onResult(new CommonMsg(BtConstants.WRITE_ERROR, error));
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (clientListener != null) {
 | 
			
		||||
                            clientListener.onResult(new CommonMsg(BtConstants.WRITE_ERROR, e.getMessage()));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    writeStatus.set(WRITE_STATUS_IDLE);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onComplete() {
 | 
			
		||||
                    Log.d(TAG, "write onComplete");
 | 
			
		||||
                    if (clientListener != null) {
 | 
			
		||||
                        clientListener.onResult(new CommonMsg(BtConstants.WRITE_SUCCESS, "write success"));
 | 
			
		||||
                    }
 | 
			
		||||
                    writeStatus.set(WRITE_STATUS_IDLE);
 | 
			
		||||
                    Log.d(TAG, "doTranslate end translate : " + msg);
 | 
			
		||||
                    doTranslate();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setClientListener(BLEClientListener clientListener) {
 | 
			
		||||
        this.clientListener = clientListener;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public IBinder onBind(Intent intent) {
 | 
			
		||||
        return new BLEClientBinder();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean onUnbind(Intent intent) {
 | 
			
		||||
        if (mBtClient != null && !TextUtils.isEmpty(connectMac)) {
 | 
			
		||||
            mBtClient.disconnect(connectMac);
 | 
			
		||||
        }
 | 
			
		||||
        return super.onUnbind(intent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class BLEClientBinder extends Binder {
 | 
			
		||||
        public BLEClientService getService() {
 | 
			
		||||
            return BLEClientService.this;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
package com.common.bluetooth.service;
 | 
			
		||||
 | 
			
		||||
import android.bluetooth.BluetoothAdapter;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.callback.BtScanCallBack;
 | 
			
		||||
import com.common.bluetooth.interfaces.IBluetoothSearch;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 经典蓝牙搜索
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothClassicSearcher implements IBluetoothSearch {
 | 
			
		||||
    private static final String TAG = "BluetoothClassicSearcher";
 | 
			
		||||
    private final Context mContext;
 | 
			
		||||
    private final BluetoothAdapter mBluetoothAdapter;
 | 
			
		||||
 | 
			
		||||
    public BluetoothClassicSearcher(Context mContext, BluetoothAdapter mBluetoothAdapter) {
 | 
			
		||||
        this.mContext = mContext;
 | 
			
		||||
        this.mBluetoothAdapter = mBluetoothAdapter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void stopScan() {
 | 
			
		||||
        if (mBluetoothAdapter.isDiscovering()) {
 | 
			
		||||
            mBluetoothAdapter.cancelDiscovery();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isScanning() {
 | 
			
		||||
        return mBluetoothAdapter.isDiscovering();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void startScan(long delayTime, @Nullable BtScanCallBack callBack) {
 | 
			
		||||
        // 每次启动前,检查一下蓝牙是否已经在扫描之中
 | 
			
		||||
        stopScan();
 | 
			
		||||
        mBluetoothAdapter.startDiscovery();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,519 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2014 The Android Open Source Project
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.common.bluetooth.service;
 | 
			
		||||
 | 
			
		||||
import android.bluetooth.BluetoothAdapter;
 | 
			
		||||
import android.bluetooth.BluetoothDevice;
 | 
			
		||||
import android.bluetooth.BluetoothServerSocket;
 | 
			
		||||
import android.bluetooth.BluetoothSocket;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.Message;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.BtConstants;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class does all the work for setting up and managing Bluetooth
 | 
			
		||||
 * connections with other devices. It has a thread that listens for
 | 
			
		||||
 * incoming connections, a thread for connecting with a device, and a
 | 
			
		||||
 * thread for performing data transmissions when connected.
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothConnectService {
 | 
			
		||||
    // Debugging
 | 
			
		||||
    private static final String TAG = "BluetoothConnectService";
 | 
			
		||||
 | 
			
		||||
    // Name for the SDP record when creating server socket
 | 
			
		||||
    private static final String NAME_SECURE = "BluetoothChatSecure";
 | 
			
		||||
    private static final String NAME_INSECURE = "BluetoothChatInsecure";
 | 
			
		||||
 | 
			
		||||
    // Unique UUID for this application
 | 
			
		||||
    private final UUID CURRENT_UUID;
 | 
			
		||||
 | 
			
		||||
    // Member fields
 | 
			
		||||
    private final BluetoothAdapter mAdapter;
 | 
			
		||||
    private final Handler mHandler;
 | 
			
		||||
    private AcceptThread mSecureAcceptThread;
 | 
			
		||||
    private AcceptThread mInsecureAcceptThread;
 | 
			
		||||
    private ConnectThread mConnectThread;
 | 
			
		||||
    private ConnectedThread mConnectedThread;
 | 
			
		||||
    private int mState;
 | 
			
		||||
    private int mNewState;
 | 
			
		||||
 | 
			
		||||
    // Constants that indicate the current connection state
 | 
			
		||||
    public static final int STATE_NONE = 0;       // we're doing nothing
 | 
			
		||||
    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
 | 
			
		||||
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
 | 
			
		||||
    public static final int STATE_CONNECTED = 3;  // now connected to a remote device
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor. Prepares a new BluetoothChat session.
 | 
			
		||||
     *
 | 
			
		||||
     * @param context The UI Activity Context
 | 
			
		||||
     * @param handler A Handler to send messages back to the UI Activity
 | 
			
		||||
     */
 | 
			
		||||
    public BluetoothConnectService(Context context, Handler handler) {
 | 
			
		||||
        CURRENT_UUID = BtConstants.INSTANCE.getUUid(context);
 | 
			
		||||
        mAdapter = BluetoothAdapter.getDefaultAdapter();
 | 
			
		||||
        mState = STATE_NONE;
 | 
			
		||||
        mNewState = mState;
 | 
			
		||||
        mHandler = handler;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update UI title according to the current state of the chat connection
 | 
			
		||||
     */
 | 
			
		||||
    private synchronized void updateUserInterfaceTitle() {
 | 
			
		||||
        mState = getState();
 | 
			
		||||
        Log.d(TAG, "updateUserInterfaceTitle() " + mNewState + " -> " + mState);
 | 
			
		||||
        mNewState = mState;
 | 
			
		||||
 | 
			
		||||
        // Give the new state to the Handler so the UI Activity can update
 | 
			
		||||
        mHandler.obtainMessage(BtConstants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the current connection state.
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized int getState() {
 | 
			
		||||
        return mState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start the chat service. Specifically start AcceptThread to begin a
 | 
			
		||||
     * session in listening (server) mode. Called by the Activity onResume()
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void start() {
 | 
			
		||||
        Log.d(TAG, "start");
 | 
			
		||||
 | 
			
		||||
        // Cancel any thread attempting to make a connection
 | 
			
		||||
        if (mConnectThread != null) {
 | 
			
		||||
            mConnectThread.cancel();
 | 
			
		||||
            mConnectThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cancel any thread currently running a connection
 | 
			
		||||
        if (mConnectedThread != null) {
 | 
			
		||||
            mConnectedThread.cancel();
 | 
			
		||||
            mConnectedThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start the thread to listen on a BluetoothServerSocket
 | 
			
		||||
        if (mSecureAcceptThread == null) {
 | 
			
		||||
            mSecureAcceptThread = new AcceptThread(false);
 | 
			
		||||
            mSecureAcceptThread.start();
 | 
			
		||||
        }
 | 
			
		||||
        if (mInsecureAcceptThread == null) {
 | 
			
		||||
            mInsecureAcceptThread = new AcceptThread(false);
 | 
			
		||||
            mInsecureAcceptThread.start();
 | 
			
		||||
        }
 | 
			
		||||
        // Update UI title
 | 
			
		||||
        updateUserInterfaceTitle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start the ConnectThread to initiate a connection to a remote device.
 | 
			
		||||
     *
 | 
			
		||||
     * @param device The BluetoothDevice to connect
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void connect(BluetoothDevice device) {
 | 
			
		||||
        Log.d(TAG, "connect to: " + device);
 | 
			
		||||
 | 
			
		||||
        // Cancel any thread attempting to make a connection
 | 
			
		||||
        if (mState == STATE_CONNECTING) {
 | 
			
		||||
            if (mConnectThread != null) {
 | 
			
		||||
                mConnectThread.cancel();
 | 
			
		||||
                mConnectThread = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cancel any thread currently running a connection
 | 
			
		||||
        if (mConnectedThread != null) {
 | 
			
		||||
            mConnectedThread.cancel();
 | 
			
		||||
            mConnectedThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start the thread to connect with the given device
 | 
			
		||||
        mConnectThread = new ConnectThread(device, false);
 | 
			
		||||
        mConnectThread.start();
 | 
			
		||||
        // Update UI title
 | 
			
		||||
        updateUserInterfaceTitle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start the ConnectedThread to begin managing a Bluetooth connection
 | 
			
		||||
     *
 | 
			
		||||
     * @param socket The BluetoothSocket on which the connection was made
 | 
			
		||||
     * @param device The BluetoothDevice that has been connected
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void connected(BluetoothSocket socket, BluetoothDevice
 | 
			
		||||
            device, final String socketType) {
 | 
			
		||||
        Log.d(TAG, "connected, Socket Type:" + socketType);
 | 
			
		||||
 | 
			
		||||
        // Cancel the thread that completed the connection
 | 
			
		||||
        if (mConnectThread != null) {
 | 
			
		||||
            mConnectThread.cancel();
 | 
			
		||||
            mConnectThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cancel any thread currently running a connection
 | 
			
		||||
        if (mConnectedThread != null) {
 | 
			
		||||
            mConnectedThread.cancel();
 | 
			
		||||
            mConnectedThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cancel the accept thread because we only want to connect to one device
 | 
			
		||||
        if (mSecureAcceptThread != null) {
 | 
			
		||||
            mSecureAcceptThread.cancel();
 | 
			
		||||
            mSecureAcceptThread = null;
 | 
			
		||||
        }
 | 
			
		||||
        if (mInsecureAcceptThread != null) {
 | 
			
		||||
            mInsecureAcceptThread.cancel();
 | 
			
		||||
            mInsecureAcceptThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start the thread to manage the connection and perform transmissions
 | 
			
		||||
        mConnectedThread = new ConnectedThread(socket, socketType);
 | 
			
		||||
        mConnectedThread.start();
 | 
			
		||||
 | 
			
		||||
        // Send the name of the connected device back to the UI Activity
 | 
			
		||||
        Message msg = mHandler.obtainMessage(BtConstants.MESSAGE_DEVICE_NAME);
 | 
			
		||||
        Bundle bundle = new Bundle();
 | 
			
		||||
        bundle.putString(BtConstants.DEVICE_NAME, device.getName());
 | 
			
		||||
        msg.setData(bundle);
 | 
			
		||||
        mHandler.sendMessage(msg);
 | 
			
		||||
        // Update UI title
 | 
			
		||||
        updateUserInterfaceTitle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop all threads
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void stop() {
 | 
			
		||||
        Log.d(TAG, "stop");
 | 
			
		||||
 | 
			
		||||
        if (mConnectThread != null) {
 | 
			
		||||
            mConnectThread.cancel();
 | 
			
		||||
            mConnectThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mConnectedThread != null) {
 | 
			
		||||
            mConnectedThread.cancel();
 | 
			
		||||
            mConnectedThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mSecureAcceptThread != null) {
 | 
			
		||||
            mSecureAcceptThread.cancel();
 | 
			
		||||
            mSecureAcceptThread = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mInsecureAcceptThread != null) {
 | 
			
		||||
            mInsecureAcceptThread.cancel();
 | 
			
		||||
            mInsecureAcceptThread = null;
 | 
			
		||||
        }
 | 
			
		||||
        mState = STATE_NONE;
 | 
			
		||||
        // Update UI title
 | 
			
		||||
        updateUserInterfaceTitle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Write to the ConnectedThread in an unsynchronized manner
 | 
			
		||||
     *
 | 
			
		||||
     * @param out The bytes to write
 | 
			
		||||
     * @see ConnectedThread#write(byte[])
 | 
			
		||||
     */
 | 
			
		||||
    public void write(byte[] out) {
 | 
			
		||||
        // Create temporary object
 | 
			
		||||
        ConnectedThread r;
 | 
			
		||||
        // Synchronize a copy of the ConnectedThread
 | 
			
		||||
        synchronized (this) {
 | 
			
		||||
            if (mState != STATE_CONNECTED) return;
 | 
			
		||||
            r = mConnectedThread;
 | 
			
		||||
        }
 | 
			
		||||
        // Perform the write unsynchronized
 | 
			
		||||
        r.write(out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicate that the connection attempt failed and notify the UI Activity.
 | 
			
		||||
     */
 | 
			
		||||
    private void connectionFailed() {
 | 
			
		||||
        // Send a failure message back to the Activity
 | 
			
		||||
        Message msg = mHandler.obtainMessage(BtConstants.MESSAGE_TOAST);
 | 
			
		||||
        Bundle bundle = new Bundle();
 | 
			
		||||
        bundle.putString(BtConstants.TOAST, "Unable to connect device");
 | 
			
		||||
        msg.setData(bundle);
 | 
			
		||||
        mHandler.sendMessage(msg);
 | 
			
		||||
 | 
			
		||||
        mState = STATE_NONE;
 | 
			
		||||
        // Update UI title
 | 
			
		||||
        updateUserInterfaceTitle();
 | 
			
		||||
 | 
			
		||||
        // Start the service over to restart listening mode
 | 
			
		||||
        BluetoothConnectService.this.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicate that the connection was lost and notify the UI Activity.
 | 
			
		||||
     */
 | 
			
		||||
    private void connectionLost() {
 | 
			
		||||
        // Send a failure message back to the Activity
 | 
			
		||||
        Message msg = mHandler.obtainMessage(BtConstants.MESSAGE_TOAST);
 | 
			
		||||
        Bundle bundle = new Bundle();
 | 
			
		||||
        bundle.putString(BtConstants.TOAST, "Device connection was lost");
 | 
			
		||||
        msg.setData(bundle);
 | 
			
		||||
        mHandler.sendMessage(msg);
 | 
			
		||||
 | 
			
		||||
        mState = STATE_NONE;
 | 
			
		||||
        // Update UI title
 | 
			
		||||
        updateUserInterfaceTitle();
 | 
			
		||||
 | 
			
		||||
        // Start the service over to restart listening mode
 | 
			
		||||
        BluetoothConnectService.this.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This thread runs while listening for incoming connections. It behaves
 | 
			
		||||
     * like a server-side client. It runs until a connection is accepted
 | 
			
		||||
     * (or until cancelled).
 | 
			
		||||
     */
 | 
			
		||||
    private class AcceptThread extends Thread {
 | 
			
		||||
        // The local server socket
 | 
			
		||||
        private final BluetoothServerSocket mmServerSocket;
 | 
			
		||||
        private String mSocketType;
 | 
			
		||||
 | 
			
		||||
        public AcceptThread(boolean secure) {
 | 
			
		||||
            BluetoothServerSocket tmp = null;
 | 
			
		||||
            mSocketType = secure ? "Secure" : "Insecure";
 | 
			
		||||
 | 
			
		||||
            // Create a new listening server socket
 | 
			
		||||
            try {
 | 
			
		||||
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,
 | 
			
		||||
                        CURRENT_UUID);
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e);
 | 
			
		||||
            }
 | 
			
		||||
            mmServerSocket = tmp;
 | 
			
		||||
            mState = STATE_LISTEN;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void run() {
 | 
			
		||||
            Log.d(TAG, "Socket Type: " + mSocketType +
 | 
			
		||||
                    "BEGIN mAcceptThread" + this);
 | 
			
		||||
            setName("AcceptThread" + mSocketType);
 | 
			
		||||
 | 
			
		||||
            BluetoothSocket socket = null;
 | 
			
		||||
 | 
			
		||||
            // Listen to the server socket if we're not connected
 | 
			
		||||
            while (mState != STATE_CONNECTED) {
 | 
			
		||||
                try {
 | 
			
		||||
                    // This is a blocking call and will only return on a
 | 
			
		||||
                    // successful connection or an exception
 | 
			
		||||
                    socket = mmServerSocket.accept();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // If a connection was accepted
 | 
			
		||||
                if (socket != null) {
 | 
			
		||||
                    synchronized (BluetoothConnectService.this) {
 | 
			
		||||
                        switch (mState) {
 | 
			
		||||
                            case STATE_LISTEN:
 | 
			
		||||
                            case STATE_CONNECTING:
 | 
			
		||||
                                // Situation normal. Start the connected thread.
 | 
			
		||||
                                connected(socket, socket.getRemoteDevice(),
 | 
			
		||||
                                        mSocketType);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case STATE_NONE:
 | 
			
		||||
                            case STATE_CONNECTED:
 | 
			
		||||
                                // Either not ready or already connected. Terminate new socket.
 | 
			
		||||
                                try {
 | 
			
		||||
                                    socket.close();
 | 
			
		||||
                                } catch (IOException e) {
 | 
			
		||||
                                    Log.e(TAG, "Could not close unwanted socket", e);
 | 
			
		||||
                                }
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void cancel() {
 | 
			
		||||
            Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
 | 
			
		||||
            try {
 | 
			
		||||
                mmServerSocket.close();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This thread runs while attempting to make an outgoing connection
 | 
			
		||||
     * with a device. It runs straight through; the connection either
 | 
			
		||||
     * succeeds or fails.
 | 
			
		||||
     */
 | 
			
		||||
    private class ConnectThread extends Thread {
 | 
			
		||||
        private BluetoothSocket mmSocket;
 | 
			
		||||
        private final BluetoothDevice mmDevice;
 | 
			
		||||
        private String mSocketType;
 | 
			
		||||
 | 
			
		||||
        public ConnectThread(BluetoothDevice device, boolean secure) {
 | 
			
		||||
            mmDevice = device;
 | 
			
		||||
            BluetoothSocket tmp = null;
 | 
			
		||||
            mSocketType = secure ? "Secure" : "Insecure";
 | 
			
		||||
 | 
			
		||||
            // Get a BluetoothSocket for a connection with the
 | 
			
		||||
            // given BluetoothDevice
 | 
			
		||||
            try {
 | 
			
		||||
                tmp = device.createRfcommSocketToServiceRecord(
 | 
			
		||||
                        CURRENT_UUID);
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
 | 
			
		||||
            }
 | 
			
		||||
            mmSocket = tmp;
 | 
			
		||||
            mState = STATE_CONNECTING;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void run() {
 | 
			
		||||
            Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
 | 
			
		||||
            setName("ConnectThread" + mSocketType);
 | 
			
		||||
 | 
			
		||||
            // Always cancel discovery because it will slow down a connection
 | 
			
		||||
            mAdapter.cancelDiscovery();
 | 
			
		||||
 | 
			
		||||
            // Make a connection to the BluetoothSocket
 | 
			
		||||
            try {
 | 
			
		||||
                // This is a blocking call and will only return on a
 | 
			
		||||
                // successful connection or an exception
 | 
			
		||||
                mmSocket.connect();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                // Unable to connect; close the socket and return.
 | 
			
		||||
                Log.e(TAG, e.toString());
 | 
			
		||||
                try {
 | 
			
		||||
                    mmSocket.close();
 | 
			
		||||
                } catch (IOException ie) {
 | 
			
		||||
                    connectionFailed();
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Reset the ConnectThread because we're done
 | 
			
		||||
            synchronized (BluetoothConnectService.this) {
 | 
			
		||||
                mConnectThread = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Start the connected thread
 | 
			
		||||
            connected(mmSocket, mmDevice, mSocketType);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void cancel() {
 | 
			
		||||
            try {
 | 
			
		||||
                mmSocket.close();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This thread runs during a connection with a remote device.
 | 
			
		||||
     * It handles all incoming and outgoing transmissions.
 | 
			
		||||
     */
 | 
			
		||||
    private class ConnectedThread extends Thread {
 | 
			
		||||
        private final BluetoothSocket mmSocket;
 | 
			
		||||
        private final InputStream mmInStream;
 | 
			
		||||
        private final OutputStream mmOutStream;
 | 
			
		||||
 | 
			
		||||
        public ConnectedThread(BluetoothSocket socket, String socketType) {
 | 
			
		||||
            Log.d(TAG, "create ConnectedThread: " + socketType);
 | 
			
		||||
            mmSocket = socket;
 | 
			
		||||
            InputStream tmpIn = null;
 | 
			
		||||
            OutputStream tmpOut = null;
 | 
			
		||||
 | 
			
		||||
            // Get the BluetoothSocket input and output streams
 | 
			
		||||
            try {
 | 
			
		||||
                tmpIn = socket.getInputStream();
 | 
			
		||||
                tmpOut = socket.getOutputStream();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "temp sockets not created", e);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            mmInStream = tmpIn;
 | 
			
		||||
            mmOutStream = tmpOut;
 | 
			
		||||
            mState = STATE_CONNECTED;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void run() {
 | 
			
		||||
            Log.i(TAG, "BEGIN mConnectedThread");
 | 
			
		||||
            byte[] buffer = new byte[1024];
 | 
			
		||||
            int bytes;
 | 
			
		||||
 | 
			
		||||
            // Keep listening to the InputStream while connected
 | 
			
		||||
            while (mState == STATE_CONNECTED) {
 | 
			
		||||
                try {
 | 
			
		||||
                    // Read from the InputStream
 | 
			
		||||
                    bytes = mmInStream.read(buffer);
 | 
			
		||||
 | 
			
		||||
                    // Send the obtained bytes to the UI Activity
 | 
			
		||||
                    mHandler.obtainMessage(BtConstants.MESSAGE_READ, bytes, -1, buffer)
 | 
			
		||||
                            .sendToTarget();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    Log.e(TAG, "disconnected", e);
 | 
			
		||||
                    connectionLost();
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Write to the connected OutStream.
 | 
			
		||||
         *
 | 
			
		||||
         * @param buffer The bytes to write
 | 
			
		||||
         */
 | 
			
		||||
        public void write(byte[] buffer) {
 | 
			
		||||
            try {
 | 
			
		||||
                mmOutStream.write(buffer);
 | 
			
		||||
 | 
			
		||||
                // Share the sent message back to the UI Activity
 | 
			
		||||
                mHandler.obtainMessage(BtConstants.MESSAGE_WRITE, -1, -1, buffer)
 | 
			
		||||
                        .sendToTarget();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "Exception during write", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void cancel() {
 | 
			
		||||
            try {
 | 
			
		||||
                mmSocket.close();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                Log.e(TAG, "close() of connect socket failed", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,157 @@
 | 
			
		||||
package com.common.bluetooth.service;
 | 
			
		||||
 | 
			
		||||
import android.Manifest;
 | 
			
		||||
import android.bluetooth.BluetoothAdapter;
 | 
			
		||||
import android.bluetooth.le.ScanResult;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
import android.os.Build;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.HandlerThread;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.core.content.ContextCompat;
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.callback.BtScanCallBack;
 | 
			
		||||
import com.common.bluetooth.interfaces.IBluetoothSearch;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 蓝牙扫描服务封装类
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-10-19
 | 
			
		||||
 */
 | 
			
		||||
public class BluetoothLeSearcher implements IBluetoothSearch {
 | 
			
		||||
    private static final String TAG = "BluetoothLeSearcher";
 | 
			
		||||
 | 
			
		||||
    private final BluetoothAdapter mBluetoothAdapter;
 | 
			
		||||
 | 
			
		||||
    private final Handler mHandler;
 | 
			
		||||
 | 
			
		||||
    private final Handler mAlertHandler;
 | 
			
		||||
 | 
			
		||||
    private BtScanCallBack mScanCallback;
 | 
			
		||||
 | 
			
		||||
    private final Context mContext;
 | 
			
		||||
 | 
			
		||||
    private AtomicBoolean mScanning = new AtomicBoolean(false);
 | 
			
		||||
 | 
			
		||||
    BluetoothLeSearcher(Context context, BluetoothAdapter adapter, Handler worker) {
 | 
			
		||||
        mContext = context;
 | 
			
		||||
        mBluetoothAdapter = adapter;
 | 
			
		||||
        mHandler = worker;
 | 
			
		||||
 | 
			
		||||
        HandlerThread thread = new HandlerThread("bluetooth searcher handler");
 | 
			
		||||
        thread.start();
 | 
			
		||||
        mAlertHandler = new Handler(thread.getLooper());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private BtScanCallBack wrapCallBack(final BtScanCallBack callBack) {
 | 
			
		||||
        return new BtScanCallBack() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onComplete() {
 | 
			
		||||
                runOn(callBack::onComplete);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onError(String msg) {
 | 
			
		||||
                runOn(() -> callBack.onError(msg));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onScanResult(int callbackType, ScanResult result) {
 | 
			
		||||
                runOn(() -> callBack.onResult(result));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onBatchScanResults(List<ScanResult> results) {
 | 
			
		||||
                super.onBatchScanResults(results);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止扫描
 | 
			
		||||
     */
 | 
			
		||||
    protected void stopLeScan() {
 | 
			
		||||
        if (mScanning.get()) {
 | 
			
		||||
            mScanning.set(false);
 | 
			
		||||
            mScanCallback.onComplete();
 | 
			
		||||
 | 
			
		||||
            mAlertHandler.removeCallbacksAndMessages(null);
 | 
			
		||||
            mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
 | 
			
		||||
 | 
			
		||||
            mScanCallback = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止BLE设备扫描
 | 
			
		||||
     */
 | 
			
		||||
    public void stopScan() {
 | 
			
		||||
        runOn(this::stopScan);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void runOn(Runnable runnable) {
 | 
			
		||||
        mHandler.post(runnable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void startScan(long delayTime, @Nullable BtScanCallBack callBack) {
 | 
			
		||||
        runOn(() -> {
 | 
			
		||||
            int permissionCheck1 = ContextCompat.checkSelfPermission(mContext,
 | 
			
		||||
                    Manifest.permission.ACCESS_COARSE_LOCATION);
 | 
			
		||||
 | 
			
		||||
            int permissionCheck2 = ContextCompat.checkSelfPermission(mContext,
 | 
			
		||||
                    Manifest.permission.ACCESS_FINE_LOCATION);
 | 
			
		||||
 | 
			
		||||
            if ((permissionCheck1 | permissionCheck2) != PackageManager.PERMISSION_GRANTED
 | 
			
		||||
                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
                String err = "Cannot have location permission";
 | 
			
		||||
                Log.e(TAG, err);
 | 
			
		||||
                callBack.onError(err);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (mScanning.get()) {
 | 
			
		||||
                stopLeScan();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            mScanCallback = wrapCallBack(callBack);
 | 
			
		||||
 | 
			
		||||
            // Stops scanning after a pre-defined scan period.
 | 
			
		||||
            // 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量)
 | 
			
		||||
            mAlertHandler.removeCallbacksAndMessages(null);
 | 
			
		||||
            mAlertHandler.postDelayed(new Runnable() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void run() {
 | 
			
		||||
                    stopLeScan();
 | 
			
		||||
                }
 | 
			
		||||
            }, delayTime);
 | 
			
		||||
 | 
			
		||||
            mScanning.set(true);
 | 
			
		||||
 | 
			
		||||
            // 定义一个回调接口供扫描结束处理
 | 
			
		||||
            // 指定扫描特定的支持service的蓝牙设备
 | 
			
		||||
            // call startLeScan(UUID[],    BluetoothAdapter.LeScanCallback)
 | 
			
		||||
            // 可以使用rssi计算蓝牙设备的距离
 | 
			
		||||
            // 计算公式:
 | 
			
		||||
            // d = 10^((abs(RSSI) - A) / (10 * n))
 | 
			
		||||
            // 其中:
 | 
			
		||||
            // d - 计算所得距离
 | 
			
		||||
            // RSSI - 接收信号强度(负值)
 | 
			
		||||
            // A - 射端和接收端相隔1米时的信号强度
 | 
			
		||||
            // n - 环境衰减因子
 | 
			
		||||
            mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isScanning() {
 | 
			
		||||
        return mScanning.get();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,78 @@
 | 
			
		||||
package com.common.bluetooth.view;
 | 
			
		||||
 | 
			
		||||
import android.bluetooth.BluetoothDevice;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
 | 
			
		||||
import com.common.bluetooth.databinding.RvBtDeviceItemBinding;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 连接设备列表
 | 
			
		||||
 *
 | 
			
		||||
 * @author wangym
 | 
			
		||||
 * @since 2021-11-2
 | 
			
		||||
 */
 | 
			
		||||
public class BtDeviceListAdapter extends RecyclerView.Adapter<BtDeviceListAdapter.BtViewHolder> {
 | 
			
		||||
    private static final String TAG = "BtDeviceListAdapter";
 | 
			
		||||
    private Context context;
 | 
			
		||||
    private ArrayList<BluetoothDevice> bluetoothDevices;
 | 
			
		||||
    private RvBtDeviceItemBinding mBinding;
 | 
			
		||||
    private OnDeviceClickListener onDeviceClickListener;
 | 
			
		||||
 | 
			
		||||
    public BtDeviceListAdapter(Context context, ArrayList<BluetoothDevice> bluetoothDevices) {
 | 
			
		||||
        this.context = context;
 | 
			
		||||
        this.bluetoothDevices = bluetoothDevices;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public BtViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 | 
			
		||||
        mBinding = RvBtDeviceItemBinding.inflate(LayoutInflater.from(context));
 | 
			
		||||
        return new BtViewHolder(mBinding);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onBindViewHolder(@NonNull BtViewHolder holder, int position) {
 | 
			
		||||
        final int currentIndex = position;
 | 
			
		||||
        BluetoothDevice device = bluetoothDevices.get(position);
 | 
			
		||||
        holder.binding.btDeviceNameTv.setText("Device Name:" + device.getName());
 | 
			
		||||
        holder.binding.btDeviceMacTv.setText("Device MAC:" + device.getAddress());
 | 
			
		||||
        holder.binding.btDeviceItemRl.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onClick(View v) {
 | 
			
		||||
                if (onDeviceClickListener != null) {
 | 
			
		||||
                    onDeviceClickListener.onClick(currentIndex);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getItemCount() {
 | 
			
		||||
        return bluetoothDevices == null ? 0 : bluetoothDevices.size();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static class BtViewHolder extends RecyclerView.ViewHolder {
 | 
			
		||||
        public RvBtDeviceItemBinding binding;
 | 
			
		||||
 | 
			
		||||
        public BtViewHolder(@NonNull RvBtDeviceItemBinding binding) {
 | 
			
		||||
            super(binding.getRoot());
 | 
			
		||||
            this.binding = binding;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOnDeviceClickListener(OnDeviceClickListener onDeviceClickListener) {
 | 
			
		||||
        this.onDeviceClickListener = onDeviceClickListener;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface OnDeviceClickListener {
 | 
			
		||||
        void onClick(int index);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,69 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    tools:context="com.common.bluetooth.BtDemoActivity">
 | 
			
		||||
 | 
			
		||||
    <androidx.core.widget.NestedScrollView
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
        <LinearLayout
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
            <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
                android:id="@+id/ble_rv"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="200dp" />
 | 
			
		||||
 | 
			
		||||
            <Button
 | 
			
		||||
                android:id="@+id/search_ble"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginStart="24dp"
 | 
			
		||||
                android:layout_marginEnd="24dp"
 | 
			
		||||
                android:text="搜索蓝牙" />
 | 
			
		||||
 | 
			
		||||
            <Button
 | 
			
		||||
                android:id="@+id/build_ble_server"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginStart="24dp"
 | 
			
		||||
                android:layout_marginEnd="24dp"
 | 
			
		||||
                android:text="建立ble服务" />
 | 
			
		||||
 | 
			
		||||
            <EditText
 | 
			
		||||
                android:id="@+id/msg_et"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_margin="24dp" />
 | 
			
		||||
 | 
			
		||||
            <Button
 | 
			
		||||
                android:id="@+id/send_msg"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginStart="24dp"
 | 
			
		||||
                android:layout_marginEnd="24dp"
 | 
			
		||||
                android:layout_marginBottom="24dp"
 | 
			
		||||
                android:text="发送数据" />
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:id="@+id/receive_title"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_margin="24dp"
 | 
			
		||||
                android:text="收到的消息:" />
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:id="@+id/receive_content"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginStart="24dp"
 | 
			
		||||
                android:layout_marginEnd="24dp" />
 | 
			
		||||
        </LinearLayout>
 | 
			
		||||
    </androidx.core.widget.NestedScrollView>
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:id="@+id/bt_device_item_rl"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/bt_device_name_tv"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginStart="24dp"
 | 
			
		||||
        android:layout_marginTop="12dp"
 | 
			
		||||
        android:layout_marginEnd="24dp" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/bt_device_mac_tv"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_below="@+id/bt_device_name_tv"
 | 
			
		||||
        android:layout_marginStart="24dp"
 | 
			
		||||
        android:layout_marginTop="2dp"
 | 
			
		||||
        android:layout_marginEnd="24dp" />
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <color name="purple_200">#FFBB86FC</color>
 | 
			
		||||
    <color name="purple_500">#FF6200EE</color>
 | 
			
		||||
    <color name="purple_700">#FF3700B3</color>
 | 
			
		||||
    <color name="teal_200">#FF03DAC5</color>
 | 
			
		||||
    <color name="teal_700">#FF018786</color>
 | 
			
		||||
    <color name="black">#FF000000</color>
 | 
			
		||||
    <color name="white">#FFFFFFFF</color>
 | 
			
		||||
 | 
			
		||||
    <color name="label_color">#ffffffff</color>
 | 
			
		||||
    <color name="label_color_hl0">#ffffffff</color>
 | 
			
		||||
    <color name="balloon_color">#ff000000</color>
 | 
			
		||||
    <color name="candidate_color">#ff000000</color>
 | 
			
		||||
    <color name="active_candidate_color">#ff000000</color>
 | 
			
		||||
    <color name="recommended_candidate_color">#ffe35900</color>
 | 
			
		||||
    <color name="footnote_color">#ff343233</color>
 | 
			
		||||
    <color name="composing_color">#ff000000</color>
 | 
			
		||||
    <color name="composing_color_hl">#ffffffff</color>
 | 
			
		||||
    <color name="composing_color_idle">#ff777777</color>
 | 
			
		||||
</resources>
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
rootProject.name = "CommonLibTest"
 | 
			
		||||
include ':app'
 | 
			
		||||
include ':commonLib'
 | 
			
		||||
include ':commonbt'
 | 
			
		||||
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue