34 changed files with 1371 additions and 1833 deletions
@ -0,0 +1,383 @@ |
|||
package com.yunqiinnovation.deepsound |
|||
|
|||
import android.bluetooth.BluetoothAdapter |
|||
import android.bluetooth.BluetoothDevice |
|||
import android.bluetooth.BluetoothProfile |
|||
import android.content.Context |
|||
import android.content.pm.PackageManager |
|||
import android.os.Build |
|||
import android.util.Log |
|||
import android.Manifest |
|||
import androidx.core.content.ContextCompat |
|||
import java.util.concurrent.atomic.AtomicBoolean |
|||
import java.util.concurrent.CountDownLatch |
|||
import java.util.concurrent.TimeUnit |
|||
import android.os.Handler |
|||
import android.os.Looper |
|||
import android.content.BroadcastReceiver |
|||
import android.content.Intent |
|||
import android.content.IntentFilter |
|||
|
|||
/** |
|||
* 经典蓝牙助手类,负责蓝牙设备连接相关功能 |
|||
*/ |
|||
class ClassicBluetoothHelper(private val context: Context) { |
|||
|
|||
private val TAG = "ClassicBluetoothHelper" |
|||
|
|||
// 缓存蓝牙代理对象 |
|||
private var a2dpProxy: BluetoothProfile? = null |
|||
private var headsetProxy: BluetoothProfile? = null |
|||
|
|||
// 蓝牙状态和设备事件回调 |
|||
private var stateCallback: BluetoothStateCallback? = null |
|||
|
|||
// 蓝牙状态和设备连接广播接收器 |
|||
private var bluetoothReceiver: BroadcastReceiver? = null |
|||
|
|||
/** |
|||
* 获取已连接的A2DP设备(音频输出设备) |
|||
* @param callback 回调接口,返回设备列表或错误信息 |
|||
*/ |
|||
fun getConnectedA2dpDevices(callback: BluetoothDevicesCallback) { |
|||
getConnectedBluetoothDevices(BluetoothProfile.A2DP, callback) |
|||
} |
|||
|
|||
/** |
|||
* 获取已连接的蓝牙耳机设备 |
|||
* @param callback 回调接口,返回设备列表或错误信息 |
|||
*/ |
|||
fun getConnectedHeadsetDevices(callback: BluetoothDevicesCallback) { |
|||
getConnectedBluetoothDevices(BluetoothProfile.HEADSET, callback) |
|||
} |
|||
|
|||
/** |
|||
* 检查是否有蓝牙连接权限 |
|||
* @return 是否有权限 |
|||
*/ |
|||
private fun hasBluetoothConnectPermission(): Boolean { |
|||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|||
ContextCompat.checkSelfPermission( |
|||
context, |
|||
Manifest.permission.BLUETOOTH_CONNECT |
|||
) == PackageManager.PERMISSION_GRANTED |
|||
} else { |
|||
true |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查蓝牙是否启用 |
|||
* @return 蓝牙是否启用 |
|||
*/ |
|||
fun isBluetoothEnabled(): Boolean { |
|||
try { |
|||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() |
|||
if (bluetoothAdapter == null) { |
|||
return false |
|||
} |
|||
|
|||
return bluetoothAdapter.isEnabled |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "检查蓝牙状态失败", e) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取指定配置文件的已连接蓝牙设备 |
|||
* @param profile 蓝牙配置文件类型,如 BluetoothProfile.A2DP 或 BluetoothProfile.HEADSET |
|||
* @param callback 回调接口,返回设备列表或错误信息 |
|||
*/ |
|||
fun getConnectedBluetoothDevices(profile: Int, callback: BluetoothDevicesCallback) { |
|||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() |
|||
if (bluetoothAdapter == null) { |
|||
callback.onError("NO_ADAPTER", "没有找到蓝牙适配器") |
|||
return |
|||
} |
|||
|
|||
// 检查蓝牙权限 |
|||
if (!hasBluetoothConnectPermission()) { |
|||
callback.onError("PERMISSION_DENIED", "缺少蓝牙连接权限") |
|||
return |
|||
} |
|||
|
|||
try { |
|||
// 检查是否已经有缓存的代理对象 |
|||
val cachedProxy = when (profile) { |
|||
BluetoothProfile.A2DP -> a2dpProxy |
|||
BluetoothProfile.HEADSET -> headsetProxy |
|||
else -> null |
|||
} |
|||
|
|||
if (cachedProxy != null) { |
|||
// 使用已缓存的代理对象 |
|||
processDeviceList(cachedProxy, profile, callback) |
|||
return |
|||
} |
|||
|
|||
// 使用原子布尔值确保回调只被调用一次 |
|||
val callbackCalled = AtomicBoolean(false) |
|||
|
|||
// 使用Handler处理超时 |
|||
val handler = Handler(Looper.getMainLooper()) |
|||
val timeoutRunnable = Runnable { |
|||
if (callbackCalled.compareAndSet(false, true)) { |
|||
callback.onError("TIMEOUT", "获取蓝牙设备超时") |
|||
} |
|||
} |
|||
|
|||
// 设置3秒超时 |
|||
handler.postDelayed(timeoutRunnable, 3000) |
|||
|
|||
// 通过 getProfileProxy 获取指定 Profile (A2DP/HEADSET) 的连接状态 |
|||
bluetoothAdapter.getProfileProxy(context, |
|||
object : BluetoothProfile.ServiceListener { |
|||
override fun onServiceConnected(profileId: Int, proxy: BluetoothProfile) { |
|||
// 先移除超时处理 |
|||
handler.removeCallbacks(timeoutRunnable) |
|||
|
|||
if (profileId == profile) { |
|||
// 缓存代理对象 |
|||
when (profileId) { |
|||
BluetoothProfile.A2DP -> a2dpProxy = proxy |
|||
BluetoothProfile.HEADSET -> headsetProxy = proxy |
|||
} |
|||
|
|||
if (callbackCalled.compareAndSet(false, true)) { |
|||
try { |
|||
processDeviceList(proxy, profileId, callback) |
|||
} catch (e: Exception) { |
|||
callback.onError("DEVICE_ERROR", "处理设备信息时出错") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onServiceDisconnected(profileId: Int) { |
|||
// 清除对应的缓存代理 |
|||
when (profileId) { |
|||
BluetoothProfile.A2DP -> a2dpProxy = null |
|||
BluetoothProfile.HEADSET -> headsetProxy = null |
|||
} |
|||
} |
|||
}, profile |
|||
) |
|||
} catch (e: Exception) { |
|||
callback.onError("BLUETOOTH_ERROR", "获取蓝牙设备失败") |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 处理设备列表 |
|||
*/ |
|||
private fun processDeviceList(proxy: BluetoothProfile, profileId: Int, callback: BluetoothDevicesCallback) { |
|||
// 获取已连接设备 |
|||
val connectedDevices = proxy.connectedDevices |
|||
|
|||
// 转换为设备信息列表 |
|||
val devicesList = connectedDevices.map { device -> |
|||
mapOf( |
|||
"name" to (device.name ?: "未知设备"), |
|||
"address" to device.address |
|||
) |
|||
} |
|||
|
|||
// 返回设备列表(不关闭代理) |
|||
callback.onSuccess(devicesList) |
|||
} |
|||
|
|||
/** |
|||
* 检查是否有蓝牙权限 |
|||
*/ |
|||
fun hasBluetoothPermission(): Boolean { |
|||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|||
ContextCompat.checkSelfPermission( |
|||
context, |
|||
Manifest.permission.BLUETOOTH_CONNECT |
|||
) == PackageManager.PERMISSION_GRANTED |
|||
} else { |
|||
ContextCompat.checkSelfPermission( |
|||
context, |
|||
Manifest.permission.BLUETOOTH |
|||
) == PackageManager.PERMISSION_GRANTED |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 开始监听蓝牙状态变化 |
|||
* @param callback 蓝牙状态回调接口 |
|||
*/ |
|||
fun startBluetoothStateMonitoring(callback: BluetoothStateCallback) { |
|||
this.stateCallback = callback |
|||
|
|||
// 注册蓝牙广播接收器(合并状态变化和设备连接/断开监听) |
|||
if (bluetoothReceiver == null) { |
|||
bluetoothReceiver = object : BroadcastReceiver() { |
|||
override fun onReceive(context: Context?, intent: Intent?) { |
|||
when (intent?.action) { |
|||
// 监听蓝牙状态变化 |
|||
BluetoothAdapter.ACTION_STATE_CHANGED -> { |
|||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) |
|||
when (state) { |
|||
BluetoothAdapter.STATE_ON -> { |
|||
stateCallback?.onBluetoothStateChanged(BluetoothState.ON) |
|||
} |
|||
BluetoothAdapter.STATE_OFF -> { |
|||
stateCallback?.onBluetoothStateChanged(BluetoothState.OFF) |
|||
} |
|||
BluetoothAdapter.STATE_TURNING_ON -> { |
|||
stateCallback?.onBluetoothStateChanged(BluetoothState.TURNING_ON) |
|||
} |
|||
BluetoothAdapter.STATE_TURNING_OFF -> { |
|||
stateCallback?.onBluetoothStateChanged(BluetoothState.TURNING_OFF) |
|||
} |
|||
} |
|||
} |
|||
// 监听设备连接 |
|||
BluetoothDevice.ACTION_ACL_CONNECTED -> { |
|||
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) |
|||
} else { |
|||
@Suppress("DEPRECATION") |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) |
|||
} |
|||
|
|||
if (device != null) { |
|||
stateCallback?.onDeviceConnected( |
|||
mapOf( |
|||
"name" to (device.name ?: "未知设备"), |
|||
"address" to device.address |
|||
) |
|||
) |
|||
} |
|||
} |
|||
// 监听设备断开 |
|||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> { |
|||
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) |
|||
} else { |
|||
@Suppress("DEPRECATION") |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) |
|||
} |
|||
|
|||
if (device != null) { |
|||
stateCallback?.onDeviceDisconnected( |
|||
mapOf( |
|||
"name" to (device.name ?: "未知设备"), |
|||
"address" to device.address |
|||
) |
|||
) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 创建IntentFilter并添加所有需要监听的动作 |
|||
val filter = IntentFilter().apply { |
|||
addAction(BluetoothAdapter.ACTION_STATE_CHANGED) |
|||
addAction(BluetoothDevice.ACTION_ACL_CONNECTED) |
|||
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) |
|||
} |
|||
|
|||
context.registerReceiver(bluetoothReceiver, filter) |
|||
// 立即返回当前状态 |
|||
reportCurrentBluetoothState() |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 报告当前蓝牙状态 |
|||
*/ |
|||
private fun reportCurrentBluetoothState() { |
|||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() |
|||
if (bluetoothAdapter != null) { |
|||
val state = when (bluetoothAdapter.state) { |
|||
BluetoothAdapter.STATE_ON -> BluetoothState.ON |
|||
BluetoothAdapter.STATE_OFF -> BluetoothState.OFF |
|||
BluetoothAdapter.STATE_TURNING_ON -> BluetoothState.TURNING_ON |
|||
BluetoothAdapter.STATE_TURNING_OFF -> BluetoothState.TURNING_OFF |
|||
else -> BluetoothState.UNKNOWN |
|||
} |
|||
stateCallback?.onBluetoothStateChanged(state) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 停止监听蓝牙状态变化 |
|||
*/ |
|||
fun stopBluetoothStateMonitoring() { |
|||
// 注销蓝牙广播接收器 |
|||
if (bluetoothReceiver != null) { |
|||
try { |
|||
context.unregisterReceiver(bluetoothReceiver) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "注销蓝牙接收器失败", e) |
|||
} |
|||
bluetoothReceiver = null |
|||
} |
|||
|
|||
// 清除回调 |
|||
stateCallback = null |
|||
} |
|||
|
|||
/** |
|||
* 释放资源 |
|||
*/ |
|||
fun dispose() { |
|||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return |
|||
|
|||
// 停止蓝牙状态监听 |
|||
stopBluetoothStateMonitoring() |
|||
|
|||
// 释放A2DP代理 |
|||
a2dpProxy?.let { |
|||
try { |
|||
bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, it) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "关闭A2DP代理失败", e) |
|||
} |
|||
a2dpProxy = null |
|||
} |
|||
|
|||
// 释放HEADSET代理 |
|||
headsetProxy?.let { |
|||
try { |
|||
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, it) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "关闭HEADSET代理失败", e) |
|||
} |
|||
headsetProxy = null |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 蓝牙设备回调接口 |
|||
*/ |
|||
interface BluetoothDevicesCallback { |
|||
fun onSuccess(devices: List<Map<String, String>>) |
|||
fun onError(code: String, message: String) |
|||
} |
|||
|
|||
/** |
|||
* 蓝牙状态回调接口 |
|||
*/ |
|||
interface BluetoothStateCallback { |
|||
fun onBluetoothStateChanged(state: BluetoothState) |
|||
fun onDeviceConnected(device: Map<String, String>) |
|||
fun onDeviceDisconnected(device: Map<String, String>) |
|||
} |
|||
|
|||
/** |
|||
* 蓝牙状态枚举 |
|||
*/ |
|||
enum class BluetoothState { |
|||
UNKNOWN, |
|||
OFF, |
|||
ON, |
|||
TURNING_ON, |
|||
TURNING_OFF |
|||
} |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
package com.yunqiinnovation.deepsound |
|||
|
|||
import android.bluetooth.BluetoothAdapter |
|||
import android.bluetooth.BluetoothDevice |
|||
import android.bluetooth.BluetoothProfile |
|||
import android.content.BroadcastReceiver |
|||
import android.content.Context |
|||
import android.content.Intent |
|||
import android.content.IntentFilter |
|||
import android.util.Log |
|||
import androidx.annotation.NonNull |
|||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|||
import io.flutter.plugin.common.EventChannel |
|||
import io.flutter.plugin.common.MethodCall |
|||
import io.flutter.plugin.common.MethodChannel |
|||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler |
|||
import io.flutter.plugin.common.MethodChannel.Result |
|||
import com.yunqiinnovation.deepvoice_utils.FileLogger |
|||
|
|||
/** ClassicBluetoothPlugin */ |
|||
class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler { |
|||
private val TAG = "ClassicBluetoothPlugin" |
|||
private lateinit var channel: MethodChannel |
|||
private lateinit var eventChannel: EventChannel |
|||
private lateinit var context: Context |
|||
private lateinit var bluetoothHelper: ClassicBluetoothHelper |
|||
private var eventSink: EventChannel.EventSink? = null |
|||
|
|||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|||
context = flutterPluginBinding.applicationContext |
|||
bluetoothHelper = ClassicBluetoothHelper(context) |
|||
|
|||
// 设置方法通道 |
|||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/methods") |
|||
channel.setMethodCallHandler(this) |
|||
|
|||
// 设置事件通道(用于蓝牙状态监控) |
|||
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/events") |
|||
eventChannel.setStreamHandler(object : EventChannel.StreamHandler { |
|||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|||
eventSink = events |
|||
startBluetoothStateMonitoring() |
|||
reportCurrentBluetoothState() |
|||
} |
|||
|
|||
override fun onCancel(arguments: Any?) { |
|||
eventSink = null |
|||
bluetoothHelper.stopBluetoothStateMonitoring() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|||
when (call.method) { |
|||
"getConnectedDevices" -> { |
|||
getConnectedDevices(result) |
|||
} |
|||
"isBluetoothEnabled" -> { |
|||
result.success(bluetoothHelper.isBluetoothEnabled()) |
|||
} |
|||
"hasBluetoothPermission" -> { |
|||
result.success(bluetoothHelper.hasBluetoothPermission()) |
|||
} |
|||
else -> { |
|||
result.notImplemented() |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取所有已连接的蓝牙设备 |
|||
*/ |
|||
private fun getConnectedDevices(result: Result) { |
|||
if (!bluetoothHelper.isBluetoothEnabled()) { |
|||
result.success(emptyList<Map<String, String>>()) |
|||
return |
|||
} |
|||
|
|||
// 使用HEADSET配置文件获取设备 |
|||
bluetoothHelper.getConnectedBluetoothDevices(BluetoothProfile.HEADSET, object : ClassicBluetoothHelper.BluetoothDevicesCallback { |
|||
override fun onSuccess(devices: List<Map<String, String>>) { |
|||
result.success(devices) |
|||
} |
|||
|
|||
override fun onError(code: String, message: String) { |
|||
result.error(code, message, null) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |
|||
channel.setMethodCallHandler(null) |
|||
eventChannel.setStreamHandler(null) |
|||
|
|||
// 确保停止蓝牙状态监听 |
|||
bluetoothHelper.stopBluetoothStateMonitoring() |
|||
bluetoothHelper.dispose() |
|||
} |
|||
|
|||
/** |
|||
* 报告当前蓝牙状态 |
|||
*/ |
|||
private fun reportCurrentBluetoothState() { |
|||
try { |
|||
val adapter = BluetoothAdapter.getDefaultAdapter() |
|||
if (adapter != null) { |
|||
val stateString = when (adapter.state) { |
|||
BluetoothAdapter.STATE_ON -> "on" |
|||
BluetoothAdapter.STATE_OFF -> "off" |
|||
BluetoothAdapter.STATE_TURNING_ON -> "turningOn" |
|||
BluetoothAdapter.STATE_TURNING_OFF -> "turningOff" |
|||
else -> "unknown" |
|||
} |
|||
|
|||
// 发送当前状态 |
|||
val eventMap = mapOf( |
|||
"type" to "stateChanged", |
|||
"state" to stateString, |
|||
"timestamp" to System.currentTimeMillis() |
|||
) |
|||
|
|||
sendEvent(eventMap) |
|||
} |
|||
} catch (e: Exception) { |
|||
FileLogger.e(TAG, "蓝牙状态获取失败", e) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 开始监听蓝牙状态变化 |
|||
*/ |
|||
private fun startBluetoothStateMonitoring() { |
|||
bluetoothHelper.startBluetoothStateMonitoring(object : ClassicBluetoothHelper.BluetoothStateCallback { |
|||
override fun onBluetoothStateChanged(state: ClassicBluetoothHelper.BluetoothState) { |
|||
val stateString = when (state) { |
|||
ClassicBluetoothHelper.BluetoothState.ON -> "on" |
|||
ClassicBluetoothHelper.BluetoothState.OFF -> "off" |
|||
ClassicBluetoothHelper.BluetoothState.TURNING_ON -> "turningOn" |
|||
ClassicBluetoothHelper.BluetoothState.TURNING_OFF -> "turningOff" |
|||
ClassicBluetoothHelper.BluetoothState.UNKNOWN -> "unknown" |
|||
} |
|||
|
|||
// 发送状态变化事件 |
|||
val eventMap = mapOf( |
|||
"type" to "stateChanged", |
|||
"state" to stateString, |
|||
"timestamp" to System.currentTimeMillis() |
|||
) |
|||
|
|||
sendEvent(eventMap) |
|||
} |
|||
|
|||
override fun onDeviceConnected(device: Map<String, String>) { |
|||
FileLogger.d(TAG, "设备连接: ${device["name"]} (${device["address"]})") |
|||
|
|||
// 发送设备连接事件 |
|||
val eventMap = mapOf( |
|||
"type" to "deviceConnected", |
|||
"device" to mapOf( |
|||
"name" to (device["name"] ?: "未知设备"), |
|||
"address" to (device["address"] ?: "") |
|||
), |
|||
"timestamp" to System.currentTimeMillis() |
|||
) |
|||
|
|||
sendEvent(eventMap) |
|||
} |
|||
|
|||
override fun onDeviceDisconnected(device: Map<String, String>) { |
|||
FileLogger.d(TAG, "设备断开: ${device["name"]} (${device["address"]})") |
|||
|
|||
// 发送设备断开事件 |
|||
val eventMap = mapOf( |
|||
"type" to "deviceDisconnected", |
|||
"device" to mapOf( |
|||
"name" to (device["name"] ?: "未知设备"), |
|||
"address" to (device["address"] ?: "") |
|||
), |
|||
"timestamp" to System.currentTimeMillis() |
|||
) |
|||
|
|||
sendEvent(eventMap) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 发送事件到Flutter端 |
|||
*/ |
|||
private fun sendEvent(event: Map<String, Any>) { |
|||
try { |
|||
eventSink?.success(event) |
|||
} catch (e: Exception) { |
|||
FileLogger.e(TAG, "事件发送失败", e) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
rootProject.name = "ble_service" |
|||
@ -1,311 +1,227 @@ |
|||
package com.yunqiinnovation.ble_service |
|||
|
|||
import android.app.* |
|||
import android.bluetooth.BluetoothManager |
|||
import android.content.Intent |
|||
import android.content.pm.ServiceInfo |
|||
import android.os.Build |
|||
import android.os.IBinder |
|||
import android.os.Handler |
|||
import android.os.Looper |
|||
import androidx.annotation.RequiresApi |
|||
import androidx.core.app.NotificationCompat |
|||
import com.yunqiinnovation.deepvoice_utils.FileLogger |
|||
import android.content.Context |
|||
import android.content.SharedPreferences |
|||
import kotlinx.coroutines.* |
|||
|
|||
/** |
|||
* BLE前台服务 |
|||
* 1. 在onCreate中启动前台服务并注册后台扫描 |
|||
* 2. 在onStartCommand中处理被BleWakeReceiver拉起的重连请求 |
|||
* 3. 在onDestroy中清理资源 |
|||
* 4. 自动扫描连接上一次连接的设备 |
|||
* 5. 响应唤醒命令,自动开启录音功能 |
|||
* 职责: |
|||
* 1. 维持前台服务状态,确保系统不会杀死应用 |
|||
* 2. 作为BLE被唤醒的服务入口 |
|||
* 3. 在被唤醒时检查连接状态,如果未连接则使用上次成功的连接重新建立连接 |
|||
*/ |
|||
@RequiresApi(26) |
|||
class BleForegroundService : Service(), BleService.Callback { |
|||
|
|||
private lateinit var bleManager: BleManagerWrapper |
|||
private val TAG = "BleForegroundService" |
|||
private val handler = Handler(Looper.getMainLooper()) |
|||
private lateinit var prefs: SharedPreferences |
|||
companion object { |
|||
private const val TAG = "BleForegroundService" |
|||
private const val RECONNECT_DELAY = 5000L |
|||
private const val CHANNEL_ID = "ble_service_channel" |
|||
private const val NOTIFICATION_ID = 1001 |
|||
} |
|||
|
|||
// 扫描相关 |
|||
private var isAutoConnecting = false |
|||
private var scanRetryCount = 0 |
|||
private val MAX_SCAN_RETRY = 3 |
|||
private val SCAN_TIMEOUT = 10000L // 10秒 |
|||
private val AUTO_CONNECT_DELAY = 2000L // 2秒 |
|||
private lateinit var bleManager: BleManagerWrapper |
|||
// 简化协程作用域:使用SupervisorJob确保一个协程失败不会影响其他协程 |
|||
private val serviceScope = CoroutineScope(SupervisorJob()) |
|||
|
|||
override fun onCreate() { |
|||
super.onCreate() |
|||
FileLogger.init(this) |
|||
FileLogger.i(TAG, "BleForegroundService正在创建...") |
|||
|
|||
// 初始化偏好设置存储 |
|||
prefs = getSharedPreferences("ble_service_prefs", Context.MODE_PRIVATE) |
|||
|
|||
// 使用全局BleManagerWrapper单例,共享BleService实例 |
|||
// 初始化BLE管理器 |
|||
bleManager = BleManagerWrapper.instance.apply { |
|||
init(this@BleForegroundService) |
|||
addCallback(this@BleForegroundService) // 注册回调 |
|||
addCallback(this@BleForegroundService) |
|||
} |
|||
|
|||
// 使用正确的前台服务类型启动 |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+ |
|||
startForeground( |
|||
BleConst.FOREGROUND_ID, |
|||
buildNotification("BLE服务运行中..."), |
|||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE |
|||
) |
|||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10-13 |
|||
startForeground( |
|||
BleConst.FOREGROUND_ID, |
|||
buildNotification("BLE服务运行中..."), |
|||
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION |
|||
) |
|||
} else { // 较旧版本 |
|||
startForeground(BleConst.FOREGROUND_ID, buildNotification("BLE服务运行中...")) |
|||
} |
|||
|
|||
// 启动自动连接流程 |
|||
startAutoConnect() |
|||
} |
|||
|
|||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
|||
// 若由Receiver带MAC拉起,则立即重连 |
|||
val mac = intent?.getStringExtra("mac") |
|||
FileLogger.d(TAG, "onStartCommand: $mac") |
|||
FileLogger.i(TAG, "服务onStartCommand: ${intent?.action}") |
|||
|
|||
// 1. 启动前台服务 |
|||
startForegroundServiceWithNotification() |
|||
|
|||
// 2. 注册后台扫描 |
|||
registerBackgroundScan() |
|||
|
|||
mac?.let { |
|||
FileLogger.i(TAG, "收到连接请求,正在连接设备: $it") |
|||
// 保存最后连接的设备地址 |
|||
saveLastConnectedDevice(it) |
|||
// 连接设备 |
|||
bleManager.connect(it) |
|||
// 3. 处理连接请求 |
|||
val macAddress = intent?.getStringExtra("mac") |
|||
if (!macAddress.isNullOrEmpty()) { |
|||
// 检查当前连接状态,如果未连接则发起连接 |
|||
serviceScope.launch { |
|||
if (!bleManager.isConnected()) { |
|||
FileLogger.i(TAG, "开始连接到设备: $macAddress") |
|||
connectToDevice(macAddress) |
|||
} else { |
|||
FileLogger.i(TAG, "设备已连接,忽略连接请求") |
|||
} |
|||
} |
|||
} |
|||
|
|||
return START_STICKY |
|||
} |
|||
|
|||
// 简化协程启动,将注册逻辑提取为独立方法 |
|||
private fun registerBackgroundScan() { |
|||
serviceScope.launch { |
|||
try { |
|||
bleManager.registerBackgroundScan() |
|||
FileLogger.i(TAG, "已注册后台扫描") |
|||
} catch (e: Exception) { |
|||
FileLogger.e(TAG, "注册后台扫描失败", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onBind(i: Intent?): IBinder? = null |
|||
|
|||
override fun onDestroy() { |
|||
FileLogger.i(TAG, "BleForegroundService正在销毁...") |
|||
// 移除回调 |
|||
bleManager.removeCallback(this) |
|||
// 取消所有待处理的操作 |
|||
handler.removeCallbacksAndMessages(null) |
|||
super.onDestroy() |
|||
} |
|||
|
|||
/** |
|||
* 启动自动连接流程 |
|||
*/ |
|||
private fun startAutoConnect() { |
|||
// 如果已经连接则不需要自动连接 |
|||
if (bleManager.isConnected()) { |
|||
FileLogger.i(TAG, "已连接到设备,无需自动连接") |
|||
return |
|||
} |
|||
// 清理BLE资源 |
|||
cleanupBleResources() |
|||
|
|||
// 获取上次连接的设备 |
|||
val lastDevice = getLastConnectedDevice() |
|||
if (lastDevice.isNullOrEmpty()) { |
|||
FileLogger.i(TAG, "无上次连接设备记录,开始扫描新设备") |
|||
startScanForDevices() |
|||
return |
|||
} |
|||
// 取消所有协程 |
|||
serviceScope.cancel() |
|||
|
|||
// 尝试连接上次的设备 |
|||
FileLogger.i(TAG, "尝试连接上次设备: $lastDevice") |
|||
bleManager.connect(lastDevice) |
|||
super.onDestroy() |
|||
} |
|||
|
|||
// 设置连接超时,如果超时则开始扫描 |
|||
handler.postDelayed({ |
|||
if (!bleManager.isConnected()) { |
|||
FileLogger.i(TAG, "连接上次设备超时,开始扫描新设备") |
|||
startScanForDevices() |
|||
// 封装资源清理逻辑 |
|||
private fun cleanupBleResources() { |
|||
serviceScope.launch { |
|||
try { |
|||
// 关闭蓝牙连接 |
|||
if (bleManager.isConnected()) { |
|||
bleManager.disconnect() |
|||
} |
|||
// 清理资源 |
|||
bleManager.cleanup() |
|||
FileLogger.i(TAG, "已清理BLE资源") |
|||
} catch (e: Exception) { |
|||
FileLogger.e(TAG, "清理BLE资源失败", e) |
|||
} finally { |
|||
// 移除回调 |
|||
bleManager.removeCallback(this@BleForegroundService) |
|||
} |
|||
}, AUTO_CONNECT_DELAY) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 开始扫描设备 |
|||
* 启动前台服务 |
|||
*/ |
|||
private fun startScanForDevices() { |
|||
if (isAutoConnecting) return |
|||
|
|||
isAutoConnecting = true |
|||
scanRetryCount = 0 |
|||
private fun startForegroundServiceWithNotification() { |
|||
val notification = createNotification() |
|||
|
|||
FileLogger.i(TAG, "开始扫描BLE设备...") |
|||
bleManager.startScan(SCAN_TIMEOUT) |
|||
|
|||
// 设置扫描超时 |
|||
handler.postDelayed({ |
|||
if (isAutoConnecting && scanRetryCount < MAX_SCAN_RETRY) { |
|||
scanRetryCount++ |
|||
FileLogger.i(TAG, "扫描超时,重试 $scanRetryCount/$MAX_SCAN_RETRY") |
|||
bleManager.startScan(SCAN_TIMEOUT) |
|||
} else if (isAutoConnecting) { |
|||
FileLogger.i(TAG, "已达最大重试次数,停止自动连接") |
|||
isAutoConnecting = false |
|||
} |
|||
}, SCAN_TIMEOUT + 1000) // 比扫描超时多1秒 |
|||
} |
|||
// 根据Android版本使用正确的前台服务类型 |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+ |
|||
startForeground( |
|||
NOTIFICATION_ID, |
|||
notification, |
|||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE |
|||
) |
|||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10-13 |
|||
startForeground( |
|||
NOTIFICATION_ID, |
|||
notification, |
|||
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION |
|||
) |
|||
} else { // 较旧版本 |
|||
startForeground(NOTIFICATION_ID, notification) |
|||
} |
|||
|
|||
/** |
|||
* 保存最后连接的设备 |
|||
*/ |
|||
private fun saveLastConnectedDevice(address: String) { |
|||
prefs.edit().putString("last_connected_device", address).apply() |
|||
FileLogger.i(TAG, "已启动前台服务") |
|||
} |
|||
|
|||
/** |
|||
* 获取最后连接的设备 |
|||
* 创建通知 |
|||
*/ |
|||
private fun getLastConnectedDevice(): String? { |
|||
return prefs.getString("last_connected_device", null) |
|||
} |
|||
private fun createNotification(): Notification { |
|||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager |
|||
|
|||
// Android 8.0+ 需要创建通知渠道 |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|||
val channel = NotificationChannel( |
|||
CHANNEL_ID, |
|||
"蓝牙连接服务", |
|||
NotificationManager.IMPORTANCE_LOW |
|||
) |
|||
manager.createNotificationChannel(channel) |
|||
} |
|||
|
|||
/** 构建低优先级通知,满足5秒前台规则 */ |
|||
private fun buildNotification(msg: String): Notification { |
|||
val nm = getSystemService(NotificationManager::class.java) |
|||
if (nm.getNotificationChannel(BleConst.CHANNEL_ID) == null) { |
|||
nm.createNotificationChannel( |
|||
NotificationChannel( |
|||
BleConst.CHANNEL_ID, "BLE前台", NotificationManager.IMPORTANCE_LOW |
|||
) |
|||
// 点击通知打开MainActivity |
|||
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { |
|||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP |
|||
} |
|||
val pendingIntent = if (intent != null) { |
|||
PendingIntent.getActivity( |
|||
this, |
|||
0, |
|||
intent, |
|||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT |
|||
) |
|||
} else { |
|||
null |
|||
} |
|||
return NotificationCompat.Builder(this, BleConst.CHANNEL_ID) |
|||
.setSmallIcon(android.R.drawable.ic_dialog_info) |
|||
|
|||
return NotificationCompat.Builder(this, CHANNEL_ID) |
|||
.setContentTitle("DeepSound") |
|||
.setContentText(msg) |
|||
.setContentText("蓝牙连接服务正在后台运行") |
|||
.setSmallIcon(android.R.drawable.ic_dialog_info) |
|||
.apply { pendingIntent?.let { setContentIntent(it) } } |
|||
.build() |
|||
} |
|||
|
|||
/** |
|||
* 更新通知内容 |
|||
* 连接到指定设备 |
|||
*/ |
|||
private fun updateNotification(msg: String) { |
|||
val nm = getSystemService(NotificationManager::class.java) |
|||
nm.notify(BleConst.FOREGROUND_ID, buildNotification(msg)) |
|||
private fun connectToDevice(address: String) { |
|||
FileLogger.i(TAG, "开始连接设备: ${address.takeLast(4)}...") |
|||
|
|||
serviceScope.launch { |
|||
try { |
|||
bleManager.connect(address) |
|||
FileLogger.i(TAG, "设备连接成功: ${address.takeLast(4)}") |
|||
} catch (e: Exception) { |
|||
FileLogger.e(TAG, "连接设备失败", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ============================== BleService.Callback 接口实现 ============================== |
|||
|
|||
override fun onScanResult(devices: List<Map<String, Any>>) { |
|||
if (!isAutoConnecting || devices.isEmpty()) return |
|||
|
|||
// 自动连接第一个找到的设备 |
|||
val device = devices.firstOrNull() |
|||
device?.let { |
|||
val address = it["id"] as String |
|||
val name = it["name"] as String |
|||
|
|||
FileLogger.i(TAG, "扫描到设备: $name ($address),正在自动连接...") |
|||
saveLastConnectedDevice(address) |
|||
bleManager.connect(address) |
|||
|
|||
// 停止扫描 |
|||
bleManager.stopScan() |
|||
|
|||
// 延迟一段时间检查连接状态 |
|||
handler.postDelayed({ |
|||
if (!bleManager.isConnected()) { |
|||
FileLogger.i(TAG, "自动连接失败,继续扫描...") |
|||
if (scanRetryCount < MAX_SCAN_RETRY) { |
|||
startScanForDevices() |
|||
} else { |
|||
FileLogger.i(TAG, "已达最大重试次数,停止自动连接") |
|||
isAutoConnecting = false |
|||
} |
|||
} |
|||
}, AUTO_CONNECT_DELAY) |
|||
} |
|||
} |
|||
|
|||
override fun onConnectionStateChanged(state: Int) { |
|||
when (state) { |
|||
BleConst.STATE_CONNECTED -> { |
|||
FileLogger.i(TAG, "设备已连接") |
|||
updateNotification("已连接到BLE设备") |
|||
isAutoConnecting = false |
|||
|
|||
// 获取设备信息 |
|||
// bleManager.getDeviceVersion() |
|||
// bleManager.getBatteryInfo() |
|||
} |
|||
BleConst.STATE_DISCONNECTED -> { |
|||
FileLogger.i(TAG, "设备已断开") |
|||
updateNotification("BLE设备已断开") |
|||
|
|||
// 如果不是主动断开,尝试重新连接 |
|||
// if (!isAutoConnecting) { |
|||
// handler.postDelayed({ |
|||
// startAutoConnect() |
|||
// }, 3000) // 延迟3秒重连 |
|||
// } |
|||
} |
|||
BleConst.STATE_ERROR -> { |
|||
FileLogger.e(TAG, "设备连接错误") |
|||
updateNotification("BLE设备连接错误") |
|||
|
|||
// 连接错误,尝试重新扫描连接 |
|||
// if (!isAutoConnecting) { |
|||
// handler.postDelayed({ |
|||
// startAutoConnect() |
|||
// }, 5000) // 延迟5秒重连 |
|||
// } |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onWakeupSignalReceived() { |
|||
FileLogger.i(TAG, "收到唤醒信号,开启设备录音功能") |
|||
|
|||
|
|||
// 更新通知 |
|||
updateNotification("已唤醒,正在录音...") |
|||
} |
|||
|
|||
override fun onCommandResult(commandType: Int, success: Boolean) { |
|||
FileLogger.i(TAG, "命令结果:类型=$commandType, 成功=$success") |
|||
} |
|||
|
|||
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) { |
|||
when (commandType) { |
|||
BleConst.CMD_CONTROL_CODEC -> { |
|||
if (success) { |
|||
FileLogger.i(TAG, "编解码控制命令执行成功") |
|||
} else { |
|||
FileLogger.e(TAG, "编解码控制命令执行失败") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onDeviceInfoReceived(infoType: Int, infoData: String) { |
|||
when (infoType) { |
|||
BleConst.CMD_GET_VERSION -> { |
|||
FileLogger.i(TAG, "设备版本: $infoData") |
|||
updateNotification("已连接:版本 $infoData") |
|||
} |
|||
BleConst.CMD_GET_BATTERY_INFO -> { |
|||
val parts = infoData.split("|") |
|||
val batteryLevel = parts[0] |
|||
val isCharging = parts.getOrNull(1) == "1" |
|||
|
|||
FileLogger.i(TAG, "电池电量: $batteryLevel%, 充电中: $isCharging") |
|||
updateNotification("已连接:电量 $batteryLevel%${if (isCharging) "(充电中)" else ""}") |
|||
} |
|||
} |
|||
FileLogger.i(TAG, "收到唤醒信号") |
|||
} |
|||
|
|||
// 以下是必须实现但当前服务不需要处理的回调方法 |
|||
override fun onDataReceived(data: ByteArray) {} |
|||
override fun onScanResult(devices: List<Map<String, Any>>) {} |
|||
override fun onAudioDataReceived(data: ByteArray) {} |
|||
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {} |
|||
override fun onDeviceInfoReceived(infoType: Int, infoData: String) {} |
|||
override fun onStatusUpdate(status: Map<String, Any>) {} |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
plugins { |
|||
// Android Library 插件 |
|||
id("com.android.library") |
|||
// Kotlin Android 插件 |
|||
id("org.jetbrains.kotlin.android") |
|||
id("com.github.johnrengelman.shadow") version "8.1.1" |
|||
} |
|||
|
|||
android { |
|||
// 命名空间,对应你插件的包名(需与代码内包名保持一致) |
|||
namespace = "com.yunqiinnovation.classic_bluetooth" |
|||
|
|||
// 目标 SDK 版本 |
|||
compileSdk = 33 |
|||
|
|||
defaultConfig { |
|||
// 最低 SDK 版本 |
|||
minSdk = 21 |
|||
targetSdk = 33 |
|||
} |
|||
|
|||
// Java 语言级别兼容配置 |
|||
compileOptions { |
|||
sourceCompatibility = JavaVersion.VERSION_11 |
|||
targetCompatibility = JavaVersion.VERSION_11 |
|||
} |
|||
|
|||
// Kotlin 语言级别 |
|||
kotlinOptions { |
|||
jvmTarget = "11" |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
dependencies { |
|||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") |
|||
implementation("androidx.core:core-ktx:1.7.0") |
|||
} |
|||
@ -1 +0,0 @@ |
|||
rootProject.name = "classic_bluetooth" |
|||
@ -1,17 +0,0 @@ |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
package="com.yunqiinnovation.classic_bluetooth"> |
|||
|
|||
<!-- 蓝牙权限 --> |
|||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> |
|||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> |
|||
|
|||
<!-- Android 12+蓝牙权限 --> |
|||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> |
|||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> |
|||
|
|||
<!-- 位置权限(部分Android版本需要) --> |
|||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> |
|||
|
|||
<uses-feature android:name="android.hardware.bluetooth" android:required="false" /> |
|||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" /> |
|||
</manifest> |
|||
@ -1,266 +0,0 @@ |
|||
package com.yunqiinnovation.classic_bluetooth |
|||
|
|||
import android.bluetooth.BluetoothAdapter |
|||
import android.bluetooth.BluetoothDevice |
|||
import android.bluetooth.BluetoothProfile |
|||
import android.content.Context |
|||
import android.content.pm.PackageManager |
|||
import android.content.BroadcastReceiver |
|||
import android.content.Intent |
|||
import android.content.IntentFilter |
|||
import android.os.Build |
|||
import android.Manifest |
|||
import androidx.core.content.ContextCompat |
|||
import android.util.Log |
|||
|
|||
/** |
|||
* 经典蓝牙助手类,集成蓝牙设备管理和状态监听功能 |
|||
*/ |
|||
class ClassicBluetoothHelper(private val context: Context) { |
|||
|
|||
private val TAG = "ClassicBluetoothHelper" |
|||
|
|||
// 回调接口用于通知蓝牙事件 |
|||
private var bluetoothEventListener: BluetoothEventListener? = null |
|||
|
|||
// 蓝牙状态变化广播接收器 |
|||
private val bluetoothStateReceiver = object : BroadcastReceiver() { |
|||
override fun onReceive(context: Context?, intent: Intent?) { |
|||
when (intent?.action) { |
|||
BluetoothDevice.ACTION_ACL_CONNECTED -> { |
|||
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) |
|||
} else { |
|||
@Suppress("DEPRECATION") |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) |
|||
} |
|||
|
|||
if (device != null) { |
|||
val deviceInfo = mapOf( |
|||
"name" to (device.name ?: "未知设备"), |
|||
"address" to device.address |
|||
) |
|||
log("蓝牙设备已连接: ${deviceInfo["name"]} (${deviceInfo["address"]})") |
|||
notifyEvent("deviceConnected", deviceInfo) |
|||
} |
|||
} |
|||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> { |
|||
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) |
|||
} else { |
|||
@Suppress("DEPRECATION") |
|||
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) |
|||
} |
|||
|
|||
if (device != null) { |
|||
val deviceInfo = mapOf( |
|||
"name" to (device.name ?: "未知设备"), |
|||
"address" to device.address |
|||
) |
|||
log("蓝牙设备已断开: ${deviceInfo["name"]} (${deviceInfo["address"]})") |
|||
notifyEvent("deviceDisconnected", deviceInfo) |
|||
} |
|||
} |
|||
BluetoothAdapter.ACTION_STATE_CHANGED -> { |
|||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) |
|||
when (state) { |
|||
BluetoothAdapter.STATE_ON -> { |
|||
log("蓝牙已开启") |
|||
notifyEvent("bluetoothStateChanged", mapOf("state" to "on")) |
|||
} |
|||
BluetoothAdapter.STATE_OFF -> { |
|||
log("蓝牙已关闭") |
|||
notifyEvent("bluetoothStateChanged", mapOf("state" to "off")) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
init { |
|||
// 在构造函数中注册广播接收器 |
|||
registerBroadcastReceivers() |
|||
} |
|||
|
|||
/** |
|||
* 注册蓝牙广播接收器 |
|||
*/ |
|||
private fun registerBroadcastReceivers() { |
|||
val bluetoothFilter = IntentFilter().apply { |
|||
addAction(BluetoothDevice.ACTION_ACL_CONNECTED) |
|||
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) |
|||
addAction(BluetoothAdapter.ACTION_STATE_CHANGED) |
|||
} |
|||
|
|||
if (Build.VERSION.SDK_INT >= 34) { // Android 14 (UPSIDE_DOWN_CAKE) |
|||
context.registerReceiver(bluetoothStateReceiver, bluetoothFilter, Context.RECEIVER_NOT_EXPORTED) |
|||
} else { |
|||
context.registerReceiver(bluetoothStateReceiver, bluetoothFilter) |
|||
} |
|||
|
|||
log("已注册蓝牙状态广播接收器") |
|||
} |
|||
|
|||
/** |
|||
* 通知蓝牙事件 |
|||
*/ |
|||
private fun notifyEvent(type: String, data: Map<String, Any>) { |
|||
val event = mutableMapOf<String, Any>( |
|||
"type" to type, |
|||
"timestamp" to System.currentTimeMillis() |
|||
) |
|||
|
|||
// 添加其他数据 |
|||
if (type == "deviceConnected" || type == "deviceDisconnected") { |
|||
event["device"] = data |
|||
} else { |
|||
event.putAll(data) |
|||
} |
|||
|
|||
// 通知监听器 |
|||
bluetoothEventListener?.onBluetoothEvent(event) |
|||
} |
|||
|
|||
/** |
|||
* 设置蓝牙事件监听器 |
|||
*/ |
|||
fun setBluetoothEventListener(listener: BluetoothEventListener?) { |
|||
this.bluetoothEventListener = listener |
|||
} |
|||
|
|||
/** |
|||
* 检查蓝牙是否启用 |
|||
* @return 蓝牙是否启用 |
|||
*/ |
|||
fun isBluetoothEnabled(): Boolean { |
|||
try { |
|||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() |
|||
if (bluetoothAdapter == null) { |
|||
log("设备不支持蓝牙", true) |
|||
return false |
|||
} |
|||
|
|||
return bluetoothAdapter.isEnabled |
|||
} catch (e: Exception) { |
|||
log("检查蓝牙状态失败: ${e.message}", true) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取所有连接的经典蓝牙设备 |
|||
* @return 设备信息列表,如果出错则返回空列表 |
|||
*/ |
|||
fun getConnectedBluetoothDevices(): List<Map<String, String>> { |
|||
if (!hasBluetoothPermission()) { |
|||
log("缺少蓝牙权限", true) |
|||
return emptyList() |
|||
} |
|||
|
|||
val devices = mutableSetOf<BluetoothDevice>() |
|||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return emptyList() |
|||
|
|||
if (!bluetoothAdapter.isEnabled) { |
|||
return emptyList() |
|||
} |
|||
|
|||
try { |
|||
// 获取已配对设备 |
|||
val bondedDevices = bluetoothAdapter.bondedDevices ?: setOf() |
|||
|
|||
// 只保留已连接的设备 |
|||
devices.addAll(bondedDevices.filter { device -> |
|||
try { |
|||
val deviceClass = device.bluetoothClass |
|||
val majorDeviceClass = deviceClass.majorDeviceClass |
|||
// 检查设备是否为音频设备、穿戴设备等常见外设类型 |
|||
device.bondState == BluetoothDevice.BOND_BONDED && |
|||
(majorDeviceClass == BluetoothProfile.HEADSET || |
|||
majorDeviceClass == BluetoothProfile.A2DP || |
|||
majorDeviceClass == BluetoothProfile.GATT) |
|||
} catch (e: Exception) { |
|||
false |
|||
} |
|||
}) |
|||
|
|||
return devices.map { device -> |
|||
mapOf( |
|||
"name" to (device.name ?: "未知设备"), |
|||
"address" to device.address, |
|||
"type" to getDeviceTypeName(device) |
|||
) |
|||
} |
|||
} catch (e: Exception) { |
|||
log("获取已连接蓝牙设备失败: ${e.message}", true) |
|||
return emptyList() |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取设备类型名称 |
|||
*/ |
|||
private fun getDeviceTypeName(device: BluetoothDevice): String { |
|||
return try { |
|||
val deviceClass = device.bluetoothClass |
|||
when (deviceClass.majorDeviceClass) { |
|||
BluetoothProfile.HEADSET -> "耳机" |
|||
BluetoothProfile.A2DP -> "音频设备" |
|||
BluetoothProfile.GATT -> "低功耗设备" |
|||
else -> "其他设备" |
|||
} |
|||
} catch (e: Exception) { |
|||
"未知类型" |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查是否有蓝牙权限 |
|||
*/ |
|||
fun hasBluetoothPermission(): Boolean { |
|||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|||
ContextCompat.checkSelfPermission( |
|||
context, |
|||
Manifest.permission.BLUETOOTH_CONNECT |
|||
) == PackageManager.PERMISSION_GRANTED |
|||
} else { |
|||
ContextCompat.checkSelfPermission( |
|||
context, |
|||
Manifest.permission.BLUETOOTH |
|||
) == PackageManager.PERMISSION_GRANTED |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 简单的日志函数 |
|||
*/ |
|||
private fun log(message: String, isError: Boolean = false) { |
|||
if (isError) { |
|||
Log.e(TAG, message) |
|||
} else { |
|||
Log.d(TAG, message) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 蓝牙事件回调接口 |
|||
*/ |
|||
interface BluetoothEventListener { |
|||
fun onBluetoothEvent(event: Map<String, Any>) |
|||
} |
|||
|
|||
/** |
|||
* 释放资源 |
|||
*/ |
|||
fun dispose() { |
|||
try { |
|||
context.unregisterReceiver(bluetoothStateReceiver) |
|||
log("已注销蓝牙状态广播接收器") |
|||
} catch (e: Exception) { |
|||
log("注销蓝牙状态广播接收器失败: ${e.message}", true) |
|||
} |
|||
|
|||
// 清除监听器 |
|||
bluetoothEventListener = null |
|||
} |
|||
} |
|||
@ -1,186 +0,0 @@ |
|||
package com.yunqiinnovation.classic_bluetooth |
|||
|
|||
import androidx.annotation.NonNull |
|||
import android.Manifest |
|||
import android.app.Activity |
|||
import android.content.Context |
|||
import android.content.pm.PackageManager |
|||
import android.os.Build |
|||
import androidx.core.app.ActivityCompat |
|||
import androidx.core.content.ContextCompat |
|||
|
|||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|||
import io.flutter.embedding.engine.plugins.activity.ActivityAware |
|||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding |
|||
import io.flutter.plugin.common.MethodCall |
|||
import io.flutter.plugin.common.MethodChannel |
|||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler |
|||
import io.flutter.plugin.common.MethodChannel.Result |
|||
import io.flutter.plugin.common.EventChannel |
|||
import io.flutter.plugin.common.EventChannel.EventSink |
|||
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener |
|||
|
|||
/** ClassicBluetoothPlugin */ |
|||
class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, RequestPermissionsResultListener, ClassicBluetoothHelper.BluetoothEventListener { |
|||
private lateinit var methodChannel : MethodChannel |
|||
private lateinit var eventChannel : EventChannel |
|||
private var eventSink: EventSink? = null |
|||
|
|||
private lateinit var context: Context |
|||
private var activity: Activity? = null |
|||
private lateinit var bluetoothHelper: ClassicBluetoothHelper |
|||
|
|||
private val REQUEST_BLUETOOTH_PERMISSIONS = 1003 |
|||
private var pendingResult: Result? = null |
|||
private var pendingMethodCall: MethodCall? = null |
|||
|
|||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|||
context = flutterPluginBinding.applicationContext |
|||
|
|||
// 设置方法通道 |
|||
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/methods") |
|||
methodChannel.setMethodCallHandler(this) |
|||
|
|||
// 设置事件通道 |
|||
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/events") |
|||
eventChannel.setStreamHandler(object : EventChannel.StreamHandler { |
|||
override fun onListen(arguments: Any?, events: EventSink?) { |
|||
eventSink = events |
|||
} |
|||
|
|||
override fun onCancel(arguments: Any?) { |
|||
eventSink = null |
|||
} |
|||
}) |
|||
|
|||
// 初始化蓝牙助手 |
|||
bluetoothHelper = ClassicBluetoothHelper(context) |
|||
bluetoothHelper.setBluetoothEventListener(this) |
|||
} |
|||
|
|||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|||
when (call.method) { |
|||
"getConnectedDevices" -> { |
|||
if (!bluetoothHelper.hasBluetoothPermission()) { |
|||
if (activity != null) { |
|||
// 保存当前方法调用和结果,以便权限授予后继续执行 |
|||
pendingMethodCall = call |
|||
pendingResult = result |
|||
requestBluetoothPermissions() |
|||
} else { |
|||
result.error("PERMISSION_DENIED", "缺少必要的蓝牙权限,且无法请求权限", null) |
|||
} |
|||
return |
|||
} |
|||
|
|||
// 获取已连接的所有蓝牙设备 |
|||
val devices = bluetoothHelper.getConnectedBluetoothDevices() |
|||
result.success(devices) |
|||
} |
|||
"isBluetoothEnabled" -> { |
|||
val isEnabled = bluetoothHelper.isBluetoothEnabled() |
|||
result.success(isEnabled) |
|||
} |
|||
else -> { |
|||
result.notImplemented() |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 请求蓝牙权限 |
|||
*/ |
|||
private fun requestBluetoothPermissions() { |
|||
if (activity == null) return |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|||
ActivityCompat.requestPermissions( |
|||
activity!!, |
|||
arrayOf( |
|||
Manifest.permission.BLUETOOTH_SCAN, |
|||
Manifest.permission.BLUETOOTH_CONNECT |
|||
), |
|||
REQUEST_BLUETOOTH_PERMISSIONS |
|||
) |
|||
} else { |
|||
ActivityCompat.requestPermissions( |
|||
activity!!, |
|||
arrayOf( |
|||
Manifest.permission.BLUETOOTH, |
|||
Manifest.permission.BLUETOOTH_ADMIN |
|||
), |
|||
REQUEST_BLUETOOTH_PERMISSIONS |
|||
) |
|||
} |
|||
} |
|||
|
|||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray): Boolean { |
|||
if (requestCode == REQUEST_BLUETOOTH_PERMISSIONS) { |
|||
// 检查是否有足够的权限 |
|||
val granted = grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED } |
|||
|
|||
if (granted) { |
|||
// 权限已授予,继续执行挂起的方法调用 |
|||
pendingMethodCall?.let { call -> |
|||
pendingResult?.let { result -> |
|||
onMethodCall(call, result) |
|||
pendingMethodCall = null |
|||
pendingResult = null |
|||
} |
|||
} |
|||
} else { |
|||
// 权限被拒绝 |
|||
pendingResult?.error("PERMISSION_DENIED", "蓝牙权限被拒绝", null) |
|||
pendingMethodCall = null |
|||
pendingResult = null |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
return false |
|||
} |
|||
|
|||
override fun onAttachedToActivity(binding: ActivityPluginBinding) { |
|||
activity = binding.activity |
|||
binding.addRequestPermissionsResultListener(this) |
|||
} |
|||
|
|||
override fun onDetachedFromActivityForConfigChanges() { |
|||
activity = null |
|||
} |
|||
|
|||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { |
|||
activity = binding.activity |
|||
binding.addRequestPermissionsResultListener(this) |
|||
} |
|||
|
|||
override fun onDetachedFromActivity() { |
|||
activity = null |
|||
} |
|||
|
|||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |
|||
methodChannel.setMethodCallHandler(null) |
|||
eventChannel.setStreamHandler(null) |
|||
|
|||
// 释放资源 |
|||
bluetoothHelper.setBluetoothEventListener(null) |
|||
bluetoothHelper.dispose() |
|||
} |
|||
|
|||
override fun onBluetoothEvent(event: Map<String, Any>) { |
|||
if (eventSink == null) return |
|||
|
|||
try { |
|||
activity?.runOnUiThread { |
|||
try { |
|||
eventSink?.success(event) |
|||
} catch (e: Exception) { |
|||
// 忽略异常 |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
// 忽略异常 |
|||
} |
|||
} |
|||
} |
|||
@ -1,119 +0,0 @@ |
|||
import 'dart:async'; |
|||
|
|||
import 'package:flutter/services.dart'; |
|||
|
|||
/// 经典蓝牙设备信息 |
|||
class BluetoothDevice { |
|||
/// 设备名称 |
|||
final String name; |
|||
|
|||
/// 设备地址 |
|||
final String address; |
|||
|
|||
/// 设备类型 |
|||
final String type; |
|||
|
|||
/// 构造函数 |
|||
BluetoothDevice({ |
|||
required this.name, |
|||
required this.address, |
|||
required this.type, |
|||
}); |
|||
|
|||
/// 从Map创建设备信息 |
|||
factory BluetoothDevice.fromMap(Map<String, dynamic> map) { |
|||
return BluetoothDevice( |
|||
name: map['name'] as String? ?? '未知设备', |
|||
address: map['address'] as String? ?? '', |
|||
type: map['type'] as String? ?? '未知类型', |
|||
); |
|||
} |
|||
|
|||
/// 转换为Map |
|||
Map<String, dynamic> toMap() { |
|||
return { |
|||
'name': name, |
|||
'address': address, |
|||
'type': type, |
|||
}; |
|||
} |
|||
|
|||
@override |
|||
String toString() => '$name ($address) [$type]'; |
|||
} |
|||
|
|||
/// 蓝牙事件 |
|||
class BluetoothEvent { |
|||
/// 事件类型 |
|||
final String type; |
|||
|
|||
/// 时间戳 |
|||
final int timestamp; |
|||
|
|||
/// 事件数据 |
|||
final Map<String, dynamic> data; |
|||
|
|||
/// 构造函数 |
|||
BluetoothEvent({ |
|||
required this.type, |
|||
required this.timestamp, |
|||
required this.data, |
|||
}); |
|||
|
|||
/// 从Map创建事件 |
|||
factory BluetoothEvent.fromMap(Map<dynamic, dynamic> map) { |
|||
return BluetoothEvent( |
|||
type: map['type'] as String, |
|||
timestamp: map['timestamp'] as int, |
|||
data: Map<String, dynamic>.from(map) |
|||
..remove('type') |
|||
..remove('timestamp'), |
|||
); |
|||
} |
|||
|
|||
/// 获取关联的设备(如果有) |
|||
BluetoothDevice? get device { |
|||
if (data.containsKey('device')) { |
|||
return BluetoothDevice.fromMap(Map<String, dynamic>.from(data['device'] as Map)); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/// 获取蓝牙状态(如果是状态变化事件) |
|||
String? get bluetoothState { |
|||
if (type == 'bluetoothStateChanged') { |
|||
return data['state'] as String?; |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/// 经典蓝牙插件类 |
|||
class ClassicBluetooth { |
|||
static const MethodChannel _methodChannel = |
|||
MethodChannel('com.yunqiinnovation.classic_bluetooth/methods'); |
|||
static const EventChannel _eventChannel = |
|||
EventChannel('com.yunqiinnovation.classic_bluetooth/events'); |
|||
|
|||
static Stream<BluetoothEvent>? _eventStream; |
|||
|
|||
/// 获取蓝牙事件流 |
|||
static Stream<BluetoothEvent> get events { |
|||
_eventStream ??= _eventChannel.receiveBroadcastStream() |
|||
.map<BluetoothEvent>((dynamic event) => BluetoothEvent.fromMap(Map<dynamic, dynamic>.from(event as Map))); |
|||
return _eventStream!; |
|||
} |
|||
|
|||
/// 获取当前连接的蓝牙设备 |
|||
static Future<List<BluetoothDevice>> getConnectedDevices() async { |
|||
final List<dynamic> devices = await _methodChannel.invokeMethod('getConnectedDevices'); |
|||
return devices |
|||
.map((device) => BluetoothDevice.fromMap(Map<String, dynamic>.from(device as Map))) |
|||
.toList(); |
|||
} |
|||
|
|||
/// 检查蓝牙是否启用 |
|||
static Future<bool> isBluetoothEnabled() async { |
|||
return await _methodChannel.invokeMethod('isBluetoothEnabled'); |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
name: classic_bluetooth |
|||
description: 经典蓝牙设备管理插件 |
|||
version: 1.0.0 |
|||
homepage: https://yunqiinnovation.com |
|||
|
|||
environment: |
|||
sdk: ">=2.12.0 <3.0.0" |
|||
flutter: ">=1.20.0" |
|||
|
|||
dependencies: |
|||
flutter: |
|||
sdk: flutter |
|||
|
|||
dev_dependencies: |
|||
flutter_test: |
|||
sdk: flutter |
|||
|
|||
flutter: |
|||
plugin: |
|||
platforms: |
|||
android: |
|||
package: com.yunqiinnovation.classic_bluetooth |
|||
pluginClass: ClassicBluetoothPlugin |
|||
Binary file not shown.
Binary file not shown.
@ -1,2 +1,2 @@ |
|||
#Wed Apr 30 15:54:50 IST 2025 |
|||
#Fri May 02 14:33:07 IST 2025 |
|||
gradle.version=8.10 |
|||
|
|||
@ -1,225 +1,16 @@ |
|||
curl 'https: //ark.cn-beijing.volces.com/api/v3/bots/chat/completions' \ |
|||
-H "Authorization: Bearer 168deb3d-fd0c-4912-b9f1-aaee5c6743e6" \ |
|||
-H 'Content-Type: application/json' \ |
|||
-d '{ |
|||
"model": "bot-20250405211523-l7c9r", |
|||
"stream": true, |
|||
"stream_options": { |
|||
"include_usage": true |
|||
}, |
|||
"messages": [ |
|||
{ |
|||
"role": "system", |
|||
"content": "You are a helpful assistant." |
|||
}, |
|||
{ |
|||
"role": "user", |
|||
"content": "Hello!" |
|||
} |
|||
] |
|||
}' |
|||
intent={cmp=com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleForegroundService} |
|||
com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleWakeReceiver filter f42f2c2 |
|||
|
|||
# 1. 启动前台 Service (API 26+ 推荐用 start-foreground-service) |
|||
adb shell am start-foreground-service \ |
|||
-n com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleForegroundService |
|||
|
|||
"amap-amap-sse": { |
|||
"url": "https://mcp.amap.com/sse?key=e5fdc9605eabdeb5626f18f5721f343d" |
|||
}, |
|||
"web-search": { |
|||
"url": "http://mcp.ideapsound.com:8000/sse" |
|||
}, |
|||
"deapsound-deapsound": { |
|||
"url": "https://www.deapsound.com/sse" |
|||
} |
|||
# 2. 发送广播到 BleWakeReceiver |
|||
adb shell am broadcast \ |
|||
-a com.yunqiinnovation.ble_service.ACTION_BLE_SCAN \ |
|||
-n com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleWakeReceiver |
|||
|
|||
|
|||
curl 'https: //ark.cn-beijing.volces.com/api/v3/bots/chat/completions' \ |
|||
-H 'Content-Type: application/json' \ |
|||
-H 'Authorization: Bearer 168deb3d-fd0c-4912-b9f1-aaee5c6743e6' \ |
|||
-d '{ |
|||
"model": "bot-20250405211523-l7c9r", |
|||
"stream": true, |
|||
"messages": [ |
|||
{ |
|||
"role": "assistant", |
|||
"content": "你好呀!今天想和我聊些什么呢😄 是生活趣事、学习心得,还是其他好玩的话题?" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "你好呀!今天过得怎么样?有什么需要我帮忙的吗?或者只是想聊聊天?😊" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "明白啦!有什么需要我帮忙的吗?无论是查资料、聊天解闷,还是帮你处理手机上的任务,我随时待命哦~ 😊" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "好的!随时准备陪你测试,有什么需要尽管说哦~ 😊 你希望测试什么功能呢?" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "哎呀,听起来你有点小困扰呢!可能是因为网络波动、系统更新或者我偶尔“打瞌睡”了(开玩笑啦~)。不过别担心,我会努力保持最佳状态陪伴你!如果有什么具体问题,随时告诉我,我一定帮你解决得妥妥的!😊\n\n(或者你想测试什么功能?我随时待命哦!)" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "嘿嘿,我是你的贴心语音助理小语呀!随时为你提供帮助、陪你聊天、解答问题,还能给你加油打气哦~有什么需要尽管告诉我吧! 😊" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "我是小语呀,那个随时准备和你畅聊、给你排忧解难、陪你度过各种时光的手机全能语音助理😜 怎么啦,是不是刚才没记住呀🧐" |
|||
}, |
|||
{ |
|||
"role": "assistant", |
|||
"content": "" |
|||
}, |
|||
{ |
|||
"role": "user", |
|||
"content": "你好呀,你是谁呀?" |
|||
} |
|||
], |
|||
"temperature": 0.7, |
|||
"max_tokens": 2000, |
|||
"stream": true, |
|||
"tools": [ |
|||
{ |
|||
"type": "function", |
|||
"function": { |
|||
"name": "exit_interaction", |
|||
"description": "退出当前语音交互", |
|||
"parameters": { |
|||
"type": "object", |
|||
"properties": {}, |
|||
"required": [] |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"type": "function", |
|||
"function": { |
|||
"name": "enter_translation_mode", |
|||
"description": "用户请求进入实时翻译模式时,启动实时翻译功能", |
|||
"parameters": { |
|||
"type": "object", |
|||
"properties": {}, |
|||
"required": [] |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"type": "function", |
|||
"function": { |
|||
"name": "send_text_message", |
|||
"description": "发送短信", |
|||
"parameters": { |
|||
"type": "object", |
|||
"properties": { |
|||
"contact": { |
|||
"type": "string", |
|||
"description": "联系人姓名或电话号码" |
|||
}, |
|||
"message": { |
|||
"type": "string", |
|||
"description": "短信内容" |
|||
} |
|||
}, |
|||
"required": [ |
|||
"contact", |
|||
"message" |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"type": "function", |
|||
"function": { |
|||
"name": "make_phone_call", |
|||
"description": "拨打电话", |
|||
"parameters": { |
|||
"type": "object", |
|||
"properties": { |
|||
"contact": { |
|||
"type": "string", |
|||
"description": "联系人姓名或电话号码" |
|||
} |
|||
}, |
|||
"required": [ |
|||
"contact" |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"type": "function", |
|||
"function": { |
|||
"name": "open_app", |
|||
"description": "当用户请求打开特定应用时,启动相应应用", |
|||
"parameters": { |
|||
"type": "object", |
|||
"properties": { |
|||
"app_name": { |
|||
"type": "string", |
|||
"description": "应用名称" |
|||
} |
|||
}, |
|||
"required": [ |
|||
"app_name" |
|||
] |
|||
} |
|||
} |
|||
} |
|||
] |
|||
}' |
|||
adb shell dumpsys package com.yunqiinnovation.deepsound | grep BleWakeReceiver |
|||
|
|||
|
|||
|
|||
|
|||
curl -X POST "https://www.deapsound.com/messages?sessionId=f156e609-6875-420b-b08d-577b7db695f1" \ |
|||
-H "Accept-Charset: UTF-8" \ |
|||
-H "Accept: */*" \ |
|||
-H "User-Agent: ktor-client" \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"id": 6, |
|||
"method": "tools/call", |
|||
"params": { |
|||
"name": "tool_weather", |
|||
"arguments": { |
|||
"city": "上海" |
|||
}, |
|||
"_meta": {}, |
|||
"method": "tools/call" |
|||
}, |
|||
"jsonrpc": "2.0" |
|||
}' |
|||
|
|||
|
|||
|
|||
|
|||
curl -X POST "https://www.deapsound.com/messages?sessionId=91c6e740-9a24-48f1-8c7f-62a2f99d1006" \ |
|||
-H "Accept-Charset: UTF-8" \ |
|||
-H "Accept: */*" \ |
|||
-H "User-Agent: ktor-client" \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"method": "notifications/initialized", |
|||
"params": { |
|||
"method": "notifications/initialized" |
|||
}, |
|||
"jsonrpc": "2.0" |
|||
}' |
|||
|
|||
curl -X GET "https://www.deapsound.com/sse" \ |
|||
-H "Accept: text/event-stream,text/event-stream" \ |
|||
-H "Cache-Control: no-cache,no-store" \ |
|||
-H "Accept-Charset: UTF-8" \ |
|||
-H "User-Agent: ktor-client"{ |
|||
"app_alive": 270180271, |
|||
"build_connection_time": 919, |
|||
"disconnect_times": 1, |
|||
"is_background": false, |
|||
"last_connect_time": 919, |
|||
"last_duration": 438, |
|||
"payload_sign_time": 0, |
|||
"report_time": 1745779540.932181, |
|||
adb shell dumpsys package com.yunqiinnovation.deepsound | grep receiver |
|||
Loading…
Reference in new issue