Browse Source

修复拨打电话和提醒 自动话处理

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
84d3c255f1
  1. 24
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt
  2. 301
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  3. 96
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt

24
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt

@ -373,18 +373,18 @@ class MCPSubClient(
isConnected = false
stopHeartbeat()
if (retryCount < maxRetryAttempts) {
retryCount++
currentReconnectDelay = minOf(currentReconnectDelay * 2, maxReconnectDelay)
Log.w(TAG, "连接失败,将在 ${currentReconnectDelay}ms 后尝试重连 (尝试 $retryCount/$maxRetryAttempts)")
delay(currentReconnectDelay)
connect()
} else {
Log.e(TAG, "已达到最大重试次数($maxRetryAttempts),停止重连")
// 可以在这里添加通知或回调,告知上层连接彻底失败
}
// if (retryCount < maxRetryAttempts) {
// retryCount++
// currentReconnectDelay = minOf(currentReconnectDelay * 2, maxReconnectDelay)
//
// Log.w(TAG, "连接失败,将在 ${currentReconnectDelay}ms 后尝试重连 (尝试 $retryCount/$maxRetryAttempts)")
//
// delay(currentReconnectDelay)
// connect()
// } else {
// Log.e(TAG, "已达到最大重试次数($maxRetryAttempts),停止重连")
// // 可以在这里添加通知或回调,告知上层连接彻底失败
// }
}
/**
* 关闭连接

301
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -1,12 +1,17 @@
package com.yunqiinnovation.chat_api
import android.Manifest
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.LocationManager
import android.net.Uri
import android.os.Build
import android.provider.AlarmClock
import android.provider.CalendarContract
import android.provider.ContactsContract
import android.telephony.SmsManager
import android.util.Log
import androidx.core.content.ContextCompat
@ -295,28 +300,108 @@ private class SendTextMessageHandler(private val context: Context?) : FunctionHa
* 拨打电话处理器
*/
private class MakePhoneCallHandler(private val context: Context?) : FunctionHandler {
/**
* 根据联系人名称获取电话号码
* @param contact 联系人名称或电话号码
* @return 电话号码
*/
private fun getPhoneNumberForContact(contact: String): String {
// 如果输入的是电话号码(简单判断,包含数字和特殊符号)
if (contact.matches(Regex("^[+\\d\\s\\-()]*\$")) && contact.any { it.isDigit() }) {
// 移除非数字字符
return contact.replace(Regex("[^+\\d]"), "")
}
// 检查读取联系人权限
if (!checkPermissionAndGuide(context!!, Manifest.permission.READ_CONTACTS, "读取联系人", "访问联系人")) {
return ""
}
// 从联系人数据库查询
try {
val contentResolver = context.contentResolver
val uri = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.encode(contact)
)
val projection = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER
)
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val hasPhoneNumber = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.HAS_PHONE_NUMBER))
if (hasPhoneNumber > 0) {
val contactId = cursor.getString(cursor.getColumnIndexOrThrow(
ContactsContract.Contacts._ID))
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
val phoneProjection = arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER)
val phoneSelection = "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?"
val phoneSelectionArgs = arrayOf(contactId)
contentResolver?.query(phoneUri, phoneProjection, phoneSelection, phoneSelectionArgs, null)?.use { phoneCursor ->
if (phoneCursor.moveToFirst()) {
return phoneCursor.getString(phoneCursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.NUMBER))
}
}
}
}
}
} catch (e: Exception) {
Log.e("ChatApi", "查询联系人信息失败: ${e.message}", e)
}
return ""
}
override suspend fun handle(arguments: Map<String, Any>): String {
val contact = arguments["contact"] as? String
if (contact == null) {
return "{\"result\": \"缺少联系人参数\"}"
}
return try {
val callIntent = Intent(Intent.ACTION_DIAL).apply {
data = Uri.parse("tel:$contact")
flags = Intent.FLAG_ACTIVITY_NEW_TASK
// 检查拨打电话权限
if (!checkPermissionAndGuide(context!!,Manifest.permission.CALL_PHONE, "拨打电话", "拨打电话")) {
"{\"result\": \"需要电话权限才能拨打电话,请在权限管理中开启电话权限\"}"
}
if (context?.packageManager?.queryIntentActivities(callIntent, 0)?.isNotEmpty() == true) {
context.startActivity(callIntent)
"{\"result\": \"已发起电话呼叫\"}"
} else {
"{\"result\": \"无法拨打电话\"}"
val phoneNumber = getPhoneNumberForContact(contact)
if (phoneNumber.isEmpty()) {
"{\"result\": \"未找到联系人信息: $contact\"}"
}
// 创建拨号意图
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:$phoneNumber")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context?.startActivity(intent)
"{\"result\": \"正在拨打${contact}的电话\"}"
} catch (e: Exception) {
"{\"result\": \"拨打电话失败:${e.message}\"}"
Log.e("ChatApi", "拨打电话失败: ${e.message}", e)
"{\"result\": \"权限不足或者为查询到联系人\"}"
}
// return try {
// val callIntent = Intent(Intent.ACTION_DIAL).apply {
// data = Uri.parse("tel:$contact")
// flags = Intent.FLAG_ACTIVITY_NEW_TASK
// }
//
// if (context?.packageManager?.queryIntentActivities(callIntent, 0)?.isNotEmpty() == true) {
// context.startActivity(callIntent)
// "{\"result\": \"已发起电话呼叫\"}"
// } else {
// "{\"result\": \"无法拨打电话\"}"
// }
// } catch (e: Exception) {
// "{\"result\": \"拨打电话失败:${e.message}\"}"
// }
}
}
@ -324,6 +409,140 @@ private class MakePhoneCallHandler(private val context: Context?) : FunctionHand
* 设置提醒处理器
*/
private class SetReminderHandler(private val context: Context?) : FunctionHandler {
/**
* 获取默认日历ID
*/
private fun getDefaultCalendarId(): Long {
val projection = arrayOf(CalendarContract.Calendars._ID)
var calendarId = -1L
try {
val uri = CalendarContract.Calendars.CONTENT_URI
val selection = "(${CalendarContract.Calendars.VISIBLE} = 1) AND (${CalendarContract.Calendars.IS_PRIMARY} = 1)"
context?.contentResolver?.query(uri, projection, selection, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
calendarId = cursor.getLong(0)
} else {
// 如果没有找到主日历,尝试找任何可见的日历
context.contentResolver.query(uri, projection, "${CalendarContract.Calendars.VISIBLE} = 1", null, null)?.use { c ->
if (c.moveToFirst()) {
calendarId = c.getLong(0)
}
}
}
}
} catch (e: Exception) {
Log.e("CahtApi", "获取默认日历ID失败: ${e.message}", e)
}
return calendarId
}
/**
* 尝试创建闹钟提醒(备选方案)
*/
private fun tryCreateAlarm(title: String, content: String, timeStr: String): String {
try {
// 解析小时和分钟
val timeParts = timeStr.split(" ")[1].split(":")
val hour = timeParts[0].toInt()
val minute = timeParts[1].toInt()
val alarmIntent = Intent(AlarmClock.ACTION_SET_ALARM)
alarmIntent.putExtra(AlarmClock.EXTRA_MESSAGE, "$title: $content")
alarmIntent.putExtra(AlarmClock.EXTRA_HOUR, hour)
alarmIntent.putExtra(AlarmClock.EXTRA_MINUTES, minute)
alarmIntent.putExtra(AlarmClock.EXTRA_SKIP_UI, true) // 尝试跳过UI
alarmIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
if (alarmIntent.resolveActivity(context!!.packageManager) != null) {
context.startActivity(alarmIntent)
return "{\"result\": \"已创建闹钟提醒: $title, 时间: $timeStr\"}"
}
} catch (e: Exception) {
Log.e("CahtApi", "创建闹钟失败: ${e.message}", e)
}
return "{\"result\": \"未找到可用的日历或闹钟应用\"}"
}
/**
* 设置提醒
* @param title 提醒标题
* @param content 提醒内容(可选)
* @param timeStr 提醒时间,格式为"yyyy-MM-dd HH:mm"
* @return 操作结果
*/
fun setReminder(title: String, content: String, timeStr: String): String {
Log.d("CahtApi", "设置提醒: $title, 时间: $timeStr")
try {
// 解析时间字符串
val time:Long = try {
val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.getDefault())
dateFormat.parse(timeStr)?.time ?: System.currentTimeMillis()
} catch (e: Exception) {
Log.e("CahtApi", "解析时间失败: $timeStr, ${e.message}")
return "{\"result\": \"提醒时间格式无效,请使用'yyyy-MM-dd HH:mm'格式\"}"
}
// 检查日历权限
if (!checkPermissionAndGuide(context!!,Manifest.permission.WRITE_CALENDAR, "日历写入", "设置提醒")) {
return "{\"result\": \"需要日历权限才能创建提醒,请在权限管理中开启日历权限\"}"
}
// 直接使用ContentProvider写入日历
val endTime = time + 3600000 // 默认1小时后结束
// 获取默认日历ID
val calendarId = getDefaultCalendarId()
if (calendarId == -1L) {
Log.w("CahtApi", "未找到默认日历")
return tryCreateAlarm(title, content, timeStr)
}
// 创建事件
val eventValues = ContentValues().apply {
put(CalendarContract.Events.CALENDAR_ID, calendarId)
put(CalendarContract.Events.TITLE, title)
put(CalendarContract.Events.DESCRIPTION, content)
put(CalendarContract.Events.DTSTART, time)
put(CalendarContract.Events.DTEND, endTime)
put(CalendarContract.Events.HAS_ALARM, 1)
put(CalendarContract.Events.EVENT_TIMEZONE, java.util.TimeZone.getDefault().id)
}
val eventUri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, eventValues)
if (eventUri == null) {
Log.e("CahtApi", "插入日历事件失败")
return tryCreateAlarm(title, content, timeStr)
}
// 获取事件ID
val eventId = ContentUris.parseId(eventUri)
// 添加提醒(15分钟前提醒)
val reminderValues = ContentValues().apply {
put(CalendarContract.Reminders.EVENT_ID, eventId)
put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT)
put(CalendarContract.Reminders.MINUTES, 15) // 提前15分钟提醒
}
val reminderUri = context.contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, reminderValues)
if (reminderUri != null) {
return "{\"result\": \"已成功创建提醒: $title, 时间: $timeStr\"}"
} else {
// 仍然创建了事件,但没有提醒
return "{\"result\": \"已创建日历事件,但未设置提醒: $title\"}"
}
} catch (e: Exception) {
Log.e("CahtApi", "设置提醒失败: ${e.message}", e)
return "{\"result\": \"设置提醒失败: ${e.message}\"}"
}
}
override suspend fun handle(arguments: Map<String, Any>): String {
val title = arguments["title"] as? String
val content = arguments["content"] as? String ?: ""
@ -332,36 +551,36 @@ private class SetReminderHandler(private val context: Context?) : FunctionHandle
if (title == null || timeString == null) {
return "{\"result\": \"缺少必要参数\"}"
}
return try {
// 解析时间
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
val date = formatter.parse(timeString)
if (date == null) {
return "{\"result\": \"时间格式错误\"}"
}
// 创建日历事件
val calendarIntent = Intent(Intent.ACTION_INSERT).apply {
data = CalendarContract.Events.CONTENT_URI
putExtra(CalendarContract.Events.TITLE, title)
putExtra(CalendarContract.Events.DESCRIPTION, content)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, date.time)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, date.time + 60 * 60 * 1000) // 默认1小时
putExtra(CalendarContract.Events.HAS_ALARM, 1)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
if (context?.packageManager?.queryIntentActivities(calendarIntent, 0)?.isNotEmpty() == true) {
context.startActivity(calendarIntent)
"{\"result\": \"提醒设置成功\"}"
} else {
"{\"result\": \"无法打开日历应用\"}"
}
} catch (e: Exception) {
"{\"result\": \"设置提醒失败:${e.message}\"}"
}
return setReminder(title,content,timeString)
// return try {
// // 解析时间
// val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
// val date = formatter.parse(timeString)
//
// if (date == null) {
// return "{\"result\": \"时间格式错误\"}"
// }
//
// // 创建日历事件
// val calendarIntent = Intent(Intent.ACTION_INSERT).apply {
// data = CalendarContract.Events.CONTENT_URI
// putExtra(CalendarContract.Events.TITLE, title)
// putExtra(CalendarContract.Events.DESCRIPTION, content)
// putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, date.time)
// putExtra(CalendarContract.EXTRA_EVENT_END_TIME, date.time + 60 * 60 * 1000) // 默认1小时
// putExtra(CalendarContract.Events.HAS_ALARM, 1)
// flags = Intent.FLAG_ACTIVITY_NEW_TASK
// }
//
// if (context?.packageManager?.queryIntentActivities(calendarIntent, 0)?.isNotEmpty() == true) {
// context.startActivity(calendarIntent)
// "{\"result\": \"提醒设置成功\"}"
// } else {
// "{\"result\": \"无法打开日历应用\"}"
// }
// } catch (e: Exception) {
// "{\"result\": \"设置提醒失败:${e.message}\"}"
// }
}
}

96
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt

@ -1,5 +1,13 @@
package com.yunqiinnovation.chat_api
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import android.util.Log
import androidx.core.content.ContextCompat
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.sse.*
@ -54,4 +62,90 @@ fun createSslTrustAllClient(): HttpClient {
*/
fun createMcpHttpClient(): HttpClient {
return createSslTrustAllClient()
}
}
/**
* 检查应用是否在前台运行
* @return 应用是否在前台
*/
fun isAppInForeground(context: Context,): Boolean {
try {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses ?: return false
val packageName = context.packageName
for (appProcess in appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&& appProcess.processName == packageName) {
return true
}
}
} catch (e: Exception) {
Log.e("ChatApi", "检查应用状态失败: ${e.message}", e)
}
return false
}
/**
* 检查权限并根据应用状态采取不同行为
* @param permission 需要检查的权限
* @param permissionName 权限名称(用于显示给用户)
* @param functionName 功能名称(用于日志和提示)
* @return 是否拥有权限
*/
fun checkPermissionAndGuide(context: Context, permission: String, permissionName: String, functionName: String): Boolean {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
Log.w("ChatApi", "缺少${permissionName}权限,无法执行${functionName}")
// 检查应用是否在前台
if (isAppInForeground(context)) {
// 应用在前台,尝试直接弹出权限申请框
try {
// 首先尝试发送广播通知DeepSound应用弹出权限申请对话框
val intent = Intent("com.yunqiinnovation.deepsound.action.REQUEST_PERMISSION").apply {
setPackage(context.packageName)
putExtra("permission", permission)
putExtra("permissionName", permissionName)
putExtra("functionName", functionName)
addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
}
context.sendBroadcast(intent)
// 由于插件无法直接请求权限(需要Activity),上面的广播会让主应用来处理权限请求
// 这里设置一个短暂延迟,让用户有时间响应权限请求
Thread.sleep(500)
// 再次检查权限是否已授予(以防用户立即授予)
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) {
return true
}
} catch (e: Exception) {
Log.e("ChatApi", "请求权限失败: ${e.message}", e)
}
// 如果权限仍未授予,引导用户到权限管理页面
try {
// 尝试跳转到应用内的权限管理页面
val intent = Intent("com.yunqiinnovation.deepsound.action.PERMISSIONS_SETTINGS").apply {
setPackage(context.packageName)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
// 如果应用内权限页面不可用,则跳转到系统应用设置页面
val settingsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", context.packageName, null)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(settingsIntent)
}
} catch (e: Exception) {
Log.e("ChatApi", "打开权限设置页面失败: ${e.message}", e)
}
}
// 应用在后台或权限请求失败,只返回文字提示
return false
}
return true
}
Loading…
Cancel
Save