From 5eb0de4a513fba445c197fac3465074fc556ea5d Mon Sep 17 00:00:00 2001 From: wolfplus Date: Mon, 19 May 2025 10:20:39 +0100 Subject: [PATCH] add --- .../controllers/settings_controller.dart | 78 ++++ .../settings/views/permissions_view.dart | 324 ++++++++++++++ lib/modules/settings/views/settings_view.dart | 92 +++- lib/routes/app_pages.dart | 8 + lib/routes/app_routes.dart | 1 + .../ios/Classes/BleServicePlugin.h | 2 +- local_plugins/ble_service/pubspec.yaml | 2 +- .../android/src/main/AndroidManifest.xml | 2 + .../open_ai_service/SystemFunctionHandler.kt | 421 +++++++++++++++--- 9 files changed, 871 insertions(+), 59 deletions(-) create mode 100644 lib/modules/settings/views/permissions_view.dart diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart index 05bca4804..984376632 100644 --- a/lib/modules/settings/controllers/settings_controller.dart +++ b/lib/modules/settings/controllers/settings_controller.dart @@ -12,6 +12,9 @@ import '../../../data/models/user_Info.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:io'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'dart:async'; +import 'package:app_settings/app_settings.dart'; class SettingsController extends GetxController { // 语言设置 @@ -101,6 +104,81 @@ class SettingsController extends GetxController { final appVersion = ''.obs; final buildNumber = ''.obs; + // 权限相关 + // 权限组,用于在UI中显示 + final List> permissionGroups = [ + { + 'name': '通讯权限', + 'icon': Icons.phone_android, + 'permissions': [ + {'type': Permission.phone, 'name': '电话', 'description': '用于拨打电话功能'}, + {'type': Permission.sms, 'name': '短信', 'description': '用于发送短信功能'}, + {'type': Permission.contacts, 'name': '通讯录', 'description': '用于访问联系人'} + ] + }, + { + 'name': '日程权限', + 'icon': Icons.calendar_today, + 'permissions': [ + {'type': Permission.calendarWriteOnly, 'name': '日历写入', 'description': '用于创建日历事件和提醒'}, + {'type': Permission.calendarFullAccess, 'name': '日历完全访问', 'description': '用于读取和修改日历事件'} + ] + }, + { + 'name': '存储权限', + 'icon': Icons.storage, + 'permissions': [ + {'type': Permission.storage, 'name': '存储', 'description': '用于保存录音和文件'}, + {'type': Permission.photos, 'name': '照片', 'description': '用于选择和保存照片'} + ] + }, + { + 'name': '位置权限', + 'icon': Icons.location_on, + 'permissions': [ + {'type': Permission.location, 'name': '位置', 'description': '用于获取当前位置信息'}, + {'type': Permission.locationAlways, 'name': '后台位置', 'description': '用于后台获取位置信息'} + ] + }, + { + 'name': '媒体权限', + 'icon': Icons.mic, + 'permissions': [ + {'type': Permission.microphone, 'name': '麦克风', 'description': '用于语音识别和录音'}, + {'type': Permission.bluetooth, 'name': '蓝牙', 'description': '用于连接蓝牙设备'} + ] + } + ]; + + // 检查单个权限状态 + Future checkPermission(Permission permission) async { + return await permission.status; + } + + // 请求单个权限 + Future requestPermission(Permission permission) async { + return await permission.request(); + } + + // 打开应用设置 + void openSettings() { + openAppSettings(); + } + + // 检查所有权限状态并返回结果 + Future> checkAllPermissions() async { + Map statuses = {}; + + for (var group in permissionGroups) { + for (var perm in group['permissions']) { + Permission permission = perm['type']; + statuses[permission] = await permission.status; + } + } + + return statuses; + } + @override void onInit() { super.onInit(); diff --git a/lib/modules/settings/views/permissions_view.dart b/lib/modules/settings/views/permissions_view.dart new file mode 100644 index 000000000..55c01331b --- /dev/null +++ b/lib/modules/settings/views/permissions_view.dart @@ -0,0 +1,324 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import '../controllers/settings_controller.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'dart:ui'; + +class PermissionsView extends GetView { + const PermissionsView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Scaffold( + backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], + appBar: AppBar( + backgroundColor: isDarkMode ? Colors.black : Colors.white, + elevation: 0.5, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + size: 20.w, + color: isDarkMode ? Colors.white : Colors.black87, + ), + onPressed: () => Get.back(), + ), + title: Text( + '权限管理'.tr, + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + centerTitle: true, + ), + body: FutureBuilder>( + future: controller.checkAllPermissions(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Center(child: Text('获取权限状态失败: ${snapshot.error}'.tr)); + } + + final permissionStatuses = snapshot.data ?? {}; + + return ListView( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + children: [ + // 通讯权限组 + _buildPermissionSection('通讯权限', Icons.phone_android, [ + Permission.phone, + Permission.sms + ], permissionStatuses, isDarkMode), + + SizedBox(height: 20.h), + + // 日历权限组 + _buildPermissionSection('日历权限', Icons.calendar_today, [ + Permission.calendarFullAccess + ], permissionStatuses, isDarkMode), + + SizedBox(height: 20.h), + + // 位置权限组 + _buildPermissionSection('位置权限', Icons.location_on, [ + Permission.location + ], permissionStatuses, isDarkMode), + ], + ); + }, + ), + ); + } + + // 构建权限组区域 + Widget _buildPermissionSection(String title, IconData icon, List permissions, + Map statuses, bool isDarkMode) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 组标题 + Padding( + padding: EdgeInsets.only(left: 4.w, bottom: 8.h), + child: Text( + title.tr, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.blue[400] : Colors.blue[600], + letterSpacing: 1, + ), + ), + ), + + // 权限项容器 + Container( + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16.r), + child: BackdropFilter( + filter: isDarkMode + ? ImageFilter.blur(sigmaX: 5, sigmaY: 5) + : ImageFilter.blur(sigmaX: 0, sigmaY: 0), + child: Column( + children: _buildPermissionItems(permissions, statuses, isDarkMode), + ), + ), + ), + ), + ], + ); + } + + // 构建权限项列表 + List _buildPermissionItems(List permissions, + Map statuses, bool isDarkMode) { + final List items = []; + + // 获取描述文本 + String getDescription(Permission permission) { + switch (permission) { + case Permission.phone: + return '用于拨打电话功能'; + case Permission.sms: + return '用于发送短信功能'; + case Permission.calendarWriteOnly: + return '用于创建日历事件和提醒'; + case Permission.calendarFullAccess: + return '用于读取和管理日历事件及提醒'; + case Permission.location: + return '用于获取当前位置信息'; + default: + return '应用功能需要'; + } + } + + // 获取名称 + String getName(Permission permission) { + switch (permission) { + case Permission.phone: + return '电话'; + case Permission.sms: + return '短信'; + case Permission.calendarWriteOnly: + return '日历写入'; + case Permission.calendarFullAccess: + return '日历完整访问'; + case Permission.location: + return '位置信息'; + default: + return permission.toString(); + } + } + + for (int i = 0; i < permissions.length; i++) { + final permission = permissions[i]; + final status = statuses[permission] ?? PermissionStatus.denied; + + items.add( + _buildPermissionItem( + name: getName(permission), + description: getDescription(permission), + status: status, + permission: permission, + isDarkMode: isDarkMode, + ), + ); + + // 添加分隔线,最后一个不加 + if (i < permissions.length - 1) { + items.add(Divider( + height: 1, + color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200], + )); + } + } + + return items; + } + + // 构建单个权限项 + Widget _buildPermissionItem({ + required String name, + required String description, + required PermissionStatus status, + required Permission permission, + required bool isDarkMode, + }) { + return Padding( + padding: EdgeInsets.all(16.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name.tr, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + SizedBox(height: 4.h), + Text( + description.tr, + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.grey[600], + ), + ), + SizedBox(height: 4.h), + _buildStatusText(status, isDarkMode), + ], + ), + ), + Switch( + value: status.isGranted, + onChanged: (value) { + if (value) { + _handlePermissionAction(permission, status); + } else if (status.isGranted) { + controller.openSettings(); + } + }, + activeColor: Colors.blue[600], + ), + ], + ), + ); + } + + // 状态文本 + Widget _buildStatusText(PermissionStatus status, bool isDarkMode) { + String text; + Color color; + + if (status.isGranted) { + text = '已授权'.tr; + color = Colors.green; + } else if (status.isDenied) { + text = '未授权'.tr; + color = Colors.red; + } else if (status.isPermanentlyDenied) { + text = '需要在系统设置中开启'.tr; + color = Colors.grey; + } else if (status.isLimited) { + text = '部分授权'.tr; + color = Colors.orange; + } else { + text = '未知状态'.tr; + color = Colors.grey; + } + + return Text( + text, + style: TextStyle( + color: color, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ); + } + + // 处理权限请求 + Future _handlePermissionAction(Permission permission, PermissionStatus status) async { + if (status.isPermanentlyDenied) { + // 打开应用设置 + controller.openSettings(); + return; + } + + if (!status.isGranted) { + // 请求权限 + final result = await controller.requestPermission(permission); + + if (result.isGranted) { + Get.snackbar('成功'.tr, '权限已授予'.tr, snackPosition: SnackPosition.BOTTOM); + } else if (result.isPermanentlyDenied) { + Get.dialog( + AlertDialog( + title: Text('需要权限'.tr), + content: Text('此权限需要在设置中手动开启'.tr), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text('取消'.tr), + ), + TextButton( + onPressed: () { + Get.back(); + controller.openSettings(); + }, + child: Text('去设置'.tr), + ), + ], + ), + ); + } + + // 刷新界面 + Get.forceAppUpdate(); + } + } +} \ No newline at end of file diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 350b91b1b..b56c5d905 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -355,7 +355,7 @@ class SettingsView extends GetView { Padding( padding: EdgeInsets.only(left: 4.w, bottom: 8.h), child: Text( - 'systemSetting'.tr, // 系统设置 + 'systemSettings'.tr, // 系统设置 style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w500, @@ -586,6 +586,22 @@ class SettingsView extends GetView { // ? Colors.white.withOpacity(0.1) // : Colors.grey[200]), + // 添加权限管理设置项 + _buildSettingItem( + icon: Icons.security, + iconColor: Colors.orange, + title: '权限管理'.tr, + isDarkMode: isDarkMode, + onTap: () { + Get.toNamed(Routes.permissions); + }, + ), + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), + // 退出登录 // _buildSimpleNavigationSetting( // title: 'logout'.tr, // 退出登录 @@ -1518,4 +1534,78 @@ class SettingsView extends GetView { ), ); } + + // 添加权限管理设置项 + Widget _buildSettingItem({ + required IconData icon, + required Color iconColor, + required String title, + required bool isDarkMode, + required VoidCallback onTap, + }) { + return InkWell( + onTap: onTap, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.grey[100], + borderRadius: BorderRadius.circular(16.r), + ), + child: Icon( + icon, + size: 16.w, + color: iconColor, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.grey[100], + borderRadius: BorderRadius.circular(16.r), + ), + child: Icon( + Icons.chevron_right, + size: 16.w, + color: isDarkMode ? Colors.white70 : Colors.grey[500], + ), + ), + ], + ), + ), + ); + } } diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 558e7e361..0cf57c7b1 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -28,6 +28,7 @@ import '../modules/pairing/views/pairing_view.dart'; import '../modules/pairing/bindings/pairing_binding.dart'; import './app_routes.dart'; import '../modules/settings/views/settings_view.dart'; +import '../modules/settings/views/permissions_view.dart'; import '../modules/settings/bindings/settings_binding.dart'; import '../modules/opus_test/views/opus_test_view.dart'; import '../modules/opus_test/bindings/opus_test_binding.dart'; @@ -181,5 +182,12 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), + GetPage( + name: Routes.permissions, + page: () => const PermissionsView(), + binding: SettingsBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index d04850623..ca88986e8 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -16,6 +16,7 @@ abstract class Routes { static const OTA_UPGRADE = '/ota_upgrade'; // OTA升级 static const timbre = '/timbre'; // 音色设置 static const settings = '/settings'; // 系统设置 + static const permissions = '/permissions'; // 权限管理 static const recording = '/recording'; // 录音功能 // 会议相关 diff --git a/local_plugins/ble_service/ios/Classes/BleServicePlugin.h b/local_plugins/ble_service/ios/Classes/BleServicePlugin.h index e28dfaa93..af92be7c5 100644 --- a/local_plugins/ble_service/ios/Classes/BleServicePlugin.h +++ b/local_plugins/ble_service/ios/Classes/BleServicePlugin.h @@ -1,4 +1,4 @@ #import - + @interface BleServicePlugin : NSObject @end \ No newline at end of file diff --git a/local_plugins/ble_service/pubspec.yaml b/local_plugins/ble_service/pubspec.yaml index ee3cf328f..47cf208c7 100644 --- a/local_plugins/ble_service/pubspec.yaml +++ b/local_plugins/ble_service/pubspec.yaml @@ -21,6 +21,6 @@ flutter: platforms: android: package: com.yunqiinnovation.ble_service - pluginClass: BleServicePlugin + pluginClass: BleServicePlugin ios: pluginClass: BleServicePlugin \ No newline at end of file diff --git a/local_plugins/open_ai_service/android/src/main/AndroidManifest.xml b/local_plugins/open_ai_service/android/src/main/AndroidManifest.xml index e38cdeb21..69c9202a9 100644 --- a/local_plugins/open_ai_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/open_ai_service/android/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ + + diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt index 4911826e6..b491eb1bd 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt @@ -11,6 +11,7 @@ import android.os.Bundle import android.provider.ContactsContract import android.provider.AlarmClock import android.provider.CalendarContract +import android.provider.MediaStore import android.provider.Settings import android.telephony.SmsManager import android.util.Log @@ -22,6 +23,13 @@ import android.os.Handler import android.os.Looper import kotlinx.coroutines.withContext import kotlinx.coroutines.Dispatchers +import android.Manifest +import android.content.ContentValues +import android.content.ContentUris +import androidx.core.content.ContextCompat +import android.app.ActivityManager +import android.app.Application +import android.os.Process /** * 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能 */ @@ -67,6 +75,9 @@ class SystemFunctionHandler(private val context: Context) { registerMediaPreviousFunction(client) registerMediaNextFunction(client) + // 注册打开录音机功能 + registerOpenRecorderFunction(client) + Log.d(TAG, "所有系统功能注册完成") } catch (e: Exception) { Log.e(TAG, "注册系统功能失败: ${e.message}", e) @@ -409,6 +420,30 @@ class SystemFunctionHandler(private val context: Context) { Log.d(TAG, "已注册媒体下一首功能") } + /** + * 注册打开录音机功能 + */ + private fun registerOpenRecorderFunction(client: MCPClient) { + client.registerLocalFunction( + "open_recorder", + "打开系统录音机并开始录音", + JSONObject(""" + { + "type": "object", + "properties": {}, + "required": [] + } + """), + object : FunctionHandler { + override suspend fun handle(arguments: Map): String { + val result = openRecorder() + return "{\"result\": \"${result.message}\"}" + } + } + ) + Log.d(TAG, "已注册打开录音机功能") + } + /** * 控制媒体播放 * @param action 操作类型:"play", "pause", "previous", "next" @@ -555,6 +590,93 @@ class SystemFunctionHandler(private val context: Context) { ) } + /** + * 检查权限并根据应用状态采取不同行为 + * @param permission 需要检查的权限 + * @param permissionName 权限名称(用于显示给用户) + * @param functionName 功能名称(用于日志和提示) + * @return 是否拥有权限 + */ + private fun checkPermissionAndGuide(permission: String, permissionName: String, functionName: String): Boolean { + if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "缺少${permissionName}权限,无法执行${functionName}") + + // 检查应用是否在前台 + if (isAppInForeground()) { + // 应用在前台,尝试直接弹出权限申请框 + 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(TAG, "请求权限失败: ${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(TAG, "打开权限设置页面失败: ${e.message}", e) + } + } + // 应用在后台或权限请求失败,只返回文字提示 + return false + } + return true + } + + /** + * 检查应用是否在前台运行 + * @return 应用是否在前台 + */ + private fun isAppInForeground(): 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(TAG, "检查应用状态失败: ${e.message}", e) + } + return false + } + /** * 发送短信 * @param contact 联系人(名称或电话号码) @@ -565,6 +687,14 @@ class SystemFunctionHandler(private val context: Context) { Log.d(TAG, "发送短信给 $contact: $message") try { + // 检查发送短信权限 + if (!checkPermissionAndGuide(Manifest.permission.SEND_SMS, "发送短信", "发送短信")) { + return Result( + success = false, + message = "需要短信权限才能发送短信,请在权限管理中开启短信权限" + ) + } + val phoneNumber = getPhoneNumberForContact(contact) if (phoneNumber.isEmpty()) { return Result( @@ -599,6 +729,14 @@ class SystemFunctionHandler(private val context: Context) { Log.d(TAG, "拨打电话给 $contact") try { + // 检查拨打电话权限 + if (!checkPermissionAndGuide(Manifest.permission.CALL_PHONE, "拨打电话", "拨打电话")) { + return Result( + success = false, + message = "需要电话权限才能拨打电话,请在权限管理中开启电话权限" + ) + } + val phoneNumber = getPhoneNumberForContact(contact) if (phoneNumber.isEmpty()) { return Result( @@ -688,6 +826,11 @@ class SystemFunctionHandler(private val context: Context) { return contact.replace(Regex("[^+\\d]"), "") } + // 检查读取联系人权限 + if (!checkPermissionAndGuide(Manifest.permission.READ_CONTACTS, "读取联系人", "访问联系人")) { + return "" + } + // 从联系人数据库查询 try { val contentResolver = context.contentResolver @@ -751,55 +894,62 @@ class SystemFunctionHandler(private val context: Context) { ) } - // 使用系统日历应用创建提醒 - val intent = 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, time) - putExtra(CalendarContract.EXTRA_EVENT_END_TIME, time + 3600000) // 默认1小时 - putExtra(CalendarContract.Events.HAS_ALARM, 1) - putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY) - flags = Intent.FLAG_ACTIVITY_NEW_TASK + // 检查日历权限 + if (!checkPermissionAndGuide(Manifest.permission.WRITE_CALENDAR, "日历写入", "设置提醒")) { + return Result( + success = false, + message = "需要日历权限才能创建提醒,请在权限管理中开启日历权限" + ) + } + + // 直接使用ContentProvider写入日历 + val endTime = time + 3600000 // 默认1小时后结束 + + // 获取默认日历ID + val calendarId = getDefaultCalendarId() + if (calendarId == -1L) { + Log.w(TAG, "未找到默认日历") + 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) } - // 检查是否有处理该Intent的应用 - val calendarActivity = intent.resolveActivity(context.packageManager) - if (calendarActivity != null) { - context.startActivity(intent) + val eventUri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, eventValues) + if (eventUri == null) { + Log.e(TAG, "插入日历事件失败") + 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( success = true, - message = "已创建提醒: $title, 时间: $timeStr" + message = "已成功创建提醒: $title, 时间: $timeStr" ) } else { - // 备选方案:如果没有日历应用,尝试创建闹钟 - 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, false) - alarmIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - - if (alarmIntent.resolveActivity(context.packageManager) != null) { - context.startActivity(alarmIntent) - return Result( - success = true, - message = "已创建闹钟提醒: $title, 时间: $timeStr" - ) - } - } catch (e: Exception) { - Log.e(TAG, "创建闹钟失败: ${e.message}", e) - } - + // 仍然创建了事件,但没有提醒 return Result( - success = false, - message = "未找到可用的日历或闹钟应用" + success = true, + message = "已创建日历事件,但未设置提醒: $title" ) } } catch (e: Exception) { @@ -811,6 +961,69 @@ class SystemFunctionHandler(private val context: Context) { } } + /** + * 获取默认日历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(TAG, "获取默认日历ID失败: ${e.message}", e) + } + + return calendarId + } + + /** + * 尝试创建闹钟提醒(备选方案) + */ + private fun tryCreateAlarm(title: String, content: String, timeStr: String): Result { + 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( + success = true, + message = "已创建闹钟提醒: $title, 时间: $timeStr" + ) + } + } catch (e: Exception) { + Log.e(TAG, "创建闹钟失败: ${e.message}", e) + } + + return Result( + success = false, + message = "未找到可用的日历或闹钟应用" + ) + } + /** * 获取当前时间 * @param format 时间格式(可选) @@ -892,22 +1105,10 @@ class SystemFunctionHandler(private val context: Context) { val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager // 检查位置权限 - val fineLocationPermission = android.Manifest.permission.ACCESS_FINE_LOCATION - val coarseLocationPermission = android.Manifest.permission.ACCESS_COARSE_LOCATION - - val hasFineLocationPermission = context.checkPermission( - fineLocationPermission, android.os.Process.myPid(), android.os.Process.myUid() - ) == PackageManager.PERMISSION_GRANTED - - val hasCoarseLocationPermission = context.checkPermission( - coarseLocationPermission, android.os.Process.myPid(), android.os.Process.myUid() - ) == PackageManager.PERMISSION_GRANTED - - if (!hasFineLocationPermission && !hasCoarseLocationPermission) { - Log.d(TAG, "没有位置权限") + if (!checkPermissionAndGuide(Manifest.permission.ACCESS_FINE_LOCATION, "位置信息", "获取位置")) { return@withContext Result( success = false, - message = "需要位置权限才能获取当前位置" + message = "需要位置权限才能获取当前位置,请在权限管理中开启位置权限" ) } @@ -916,6 +1117,15 @@ class SystemFunctionHandler(private val context: Context) { !locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { Log.d(TAG, "位置服务未启用") + // 引导用户打开位置服务 + try { + val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) + } catch (e: Exception) { + Log.e(TAG, "打开位置设置失败: ${e.message}", e) + } + return@withContext Result( success = false, message = "位置服务未启用,请打开GPS或网络定位" @@ -962,6 +1172,105 @@ class SystemFunctionHandler(private val context: Context) { } } + /** + * 打开系统录音机并尝试自动开始录音 + * @return 操作结果 + */ + fun openRecorder(): Result { + Log.d(TAG, "打开系统录音机并尝试自动开始录音") + + try { + // 创建用于启动录音机应用的Intent + val recordIntent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION) + recordIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + + // 添加额外参数尝试自动开始录音(不是所有应用都支持) + recordIntent.putExtra("android.intent.extra.quickCapture", true) + + // 检查是否有应用能处理此Intent + if (recordIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(recordIntent) + return Result( + success = true, + message = "已打开录音机,尝试自动开始录音" + ) + } else { + // 尝试使用备用方法,针对不同厂商应用添加特定参数 + val commonRecorderIntents = mapOf( + "com.android.soundrecorder" to Intent().setClassName("com.android.soundrecorder", "com.android.soundrecorder.SoundRecorder").setAction("android.intent.action.RECORD_SOUND"), + "com.sec.android.app.voicenote" to Intent().setClassName("com.sec.android.app.voicenote", "com.sec.android.app.voicenote.Main").putExtra("launch_mode", "record"), + "com.miui.soundrecorder" to Intent().setClassName("com.miui.soundrecorder", "com.miui.soundrecorder.SoundRecorderActivity").putExtra("startOnBoot", true), + "com.huawei.recorder" to Intent().setClassName("com.huawei.recorder", "com.huawei.recorder.activity.MainActivity").putExtra("autoRecord", true), + "com.google.android.apps.recorder" to Intent().setClassName("com.google.android.apps.recorder", "com.google.android.apps.recorder.ui.RecorderActivity").putExtra("start_recording", true) + ) + + for ((packageName, intent) in commonRecorderIntents) { + try { + if (isPackageInstalled(packageName, context.packageManager)) { + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) + return Result( + success = true, + message = "已打开录音应用并尝试自动开始录音" + ) + } + } catch (e: Exception) { + Log.e(TAG, "尝试打开录音应用 $packageName 失败: ${e.message}") + // 继续尝试下一个应用 + } + } + + // 如果特定Intent都失败,回退到原来的方法 + val commonRecorderPackages = listOf( + "com.android.soundrecorder", + "com.sec.android.app.voicenote", + "com.miui.soundrecorder", + "com.huawei.recorder", + "com.google.android.apps.recorder" + ) + + for (packageName in commonRecorderPackages) { + try { + val packageIntent = context.packageManager.getLaunchIntentForPackage(packageName) + if (packageIntent != null) { + packageIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + // 尝试添加通用的自动开始录音参数 + packageIntent.putExtra("android.intent.extra.quickCapture", true) + context.startActivity(packageIntent) + return Result( + success = true, + message = "已打开录音应用,请手动开始录音" + ) + } + } catch (e: Exception) { + Log.e(TAG, "尝试打开录音应用 $packageName 失败: ${e.message}") + } + } + + return Result( + success = false, + message = "未找到录音应用" + ) + } + } catch (e: Exception) { + Log.e(TAG, "打开录音机失败: ${e.message}", e) + return Result( + success = false, + message = "打开录音机失败: ${e.message}" + ) + } + } + + // 检查应用是否已安装 + private fun isPackageInstalled(packageName: String, packageManager: PackageManager): Boolean { + return try { + packageManager.getPackageInfo(packageName, 0) + true + } catch (e: Exception) { + false + } + } + /** * 操作结果数据类 */