| | |
| | | import android.provider.Telephony |
| | | import android.widget.Toast |
| | | import androidx.activity.result.contract.ActivityResultContracts |
| | | import com.google.android.material.bottomnavigation.BottomNavigationView |
| | | import androidx.appcompat.app.AppCompatActivity |
| | | import androidx.core.content.ContextCompat |
| | | import androidx.navigation.findNavController |
| | | import androidx.navigation.ui.AppBarConfiguration |
| | | import androidx.navigation.ui.setupActionBarWithNavController |
| | | import androidx.navigation.ui.setupWithNavController |
| | | import com.example.firstapp.databinding.ActivityMainBinding |
| | | import com.example.firstapp.receiver.SmsReceiver |
| | | import android.Manifest |
| | | import android.content.BroadcastReceiver |
| | | import android.content.Context |
| | | import android.content.Intent |
| | | import androidx.lifecycle.ViewModelProvider |
| | | import androidx.recyclerview.widget.LinearLayoutManager |
| | | import androidx.recyclerview.widget.RecyclerView |
| | | import com.example.firstapp.adapter.MyAdapter |
| | | import android.content.pm.PackageManager |
| | | import android.net.Uri |
| | | import android.os.Build |
| | | import androidx.annotation.RequiresApi |
| | | import com.example.firstapp.activity.LoginActivity |
| | | import com.example.firstapp.core.Core |
| | | import com.example.firstapp.entity.Item |
| | | import com.example.firstapp.ui.home.HomeViewModel |
| | | import com.example.firstapp.database.entity.Code |
| | | import com.example.firstapp.database.entity.Msg |
| | | import com.example.firstapp.database.service.RetrofitClient |
| | | import com.example.firstapp.database.service.RetrofitModelClient |
| | | import com.example.firstapp.utils.Log |
| | | import kotlinx.coroutines.CoroutineScope |
| | | import kotlinx.coroutines.Dispatchers |
| | | import kotlinx.coroutines.launch |
| | | import java.text.SimpleDateFormat |
| | | import java.util.Calendar |
| | | import java.util.Date |
| | | import java.util.Locale |
| | | import android.app.AlertDialog |
| | | import com.example.firstapp.utils.CodeUtils |
| | | |
| | | class MainActivity : AppCompatActivity() { |
| | | // 安全防护关键词数组 |
| | | private var securityKeywordsList = emptyList<String>() |
| | | |
| | | private lateinit var binding: ActivityMainBinding |
| | | |
| | | private var smsReceiver:SmsReceiver? = null |
| | | private var smsReceiver: SmsReceiver? = null |
| | | |
| | | private lateinit var adapter: MyAdapter |
| | | private lateinit var homeViewModel: HomeViewModel |
| | | // 短信权限请求 |
| | | private val smsPermissionRequest = |
| | | registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> |
| | | when { |
| | | permissions.getOrDefault( |
| | | Manifest.permission.RECEIVE_SMS, false |
| | | ) && permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> { |
| | | // 两个权限都获得授权 |
| | | Toast.makeText(this, "短信权限已授予", Toast.LENGTH_SHORT).show() |
| | | registerSmsReceiver() |
| | | // 在Android O及以上版本同步最近短信 |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| | | syncRecentSms() |
| | | } |
| | | } |
| | | |
| | | |
| | | // 权限请求代码 |
| | | private val permissionRequest = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> |
| | | if (isGranted) { |
| | | // 权限授予后注册短信监听器 |
| | | registerSmsReceiver() |
| | | } else { |
| | | // 权限拒绝,提示用户 |
| | | Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show() |
| | | else -> { |
| | | // 有权限被拒绝 |
| | | showSmsPermissionExplanationDialog() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 通知权限请求 |
| | | private val notificationPermissionRequest = |
| | | registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> |
| | | if (isGranted) { |
| | | // 权限已授予 |
| | | Toast.makeText(this, "通知权限已授予,您将能收到重要提醒", Toast.LENGTH_SHORT).show() |
| | | } else { |
| | | // 权限被拒绝 |
| | | Toast.makeText(this, "通知权限被拒绝,应用将无法发送提醒通知", Toast.LENGTH_SHORT).show() |
| | | } |
| | | // 无论通知权限是否授予,都继续请求短信权限 |
| | | requestSmsPermissions() |
| | | } |
| | | |
| | | private val syncLock = Object() |
| | | |
| | | @RequiresApi(Build.VERSION_CODES.O) |
| | | override fun onCreate(savedInstanceState: Bundle?) { |
| | | super.onCreate(savedInstanceState) |
| | | |
| | | binding = ActivityMainBinding.inflate(layoutInflater) |
| | | setContentView(binding.root) |
| | | |
| | | // 在此位置初始化 homeViewModel |
| | | homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java) |
| | | |
| | | val navView: BottomNavigationView = binding.navView |
| | | |
| | | setupViews() |
| | | val navView = binding.navView |
| | | val navController = findNavController(R.id.nav_host_fragment_activity_main) |
| | | // Passing each menu ID as a set of Ids because each |
| | | // menu should be considered as top level destinations. |
| | | val appBarConfiguration = AppBarConfiguration( |
| | | setOf( |
| | | R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications |
| | | ) |
| | | ) |
| | | setupActionBarWithNavController(navController, appBarConfiguration) |
| | | |
| | | // 只保留底部导航的设置 |
| | | navView.setupWithNavController(navController) |
| | | |
| | | // 检查权限 |
| | | if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != android.content.pm.PackageManager.PERMISSION_GRANTED) { |
| | | // 请求权限 |
| | | permissionRequest.launch(Manifest.permission.READ_SMS) |
| | | } else { |
| | | // 权限已经授予,继续执行相关操作 |
| | | registerSmsReceiver() |
| | | } |
| | | // 重置提醒计划并检查是否有错过的提醒 |
| | | resetReminders() |
| | | |
| | | // 开始权限请求流程 |
| | | startPermissionsFlow() |
| | | } |
| | | |
| | | |
| | | // 模拟数据 |
| | | // val items = listOf( |
| | | // Item(1, "First Item", "This is the first item description"), |
| | | // Item(2, "Second Item", "This is the second item description"), |
| | | // Item(3, "Third Item", "This is the third item description") |
| | | // ) |
| | | // |
| | | // val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) |
| | | // recyclerView.layoutManager = LinearLayoutManager(this) |
| | | // recyclerView.adapter = MyAdapter(items) |
| | | |
| | | // var codeList = Core.code.getAllDesc() |
| | | val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) |
| | | recyclerView.layoutManager = LinearLayoutManager(this) |
| | | // recyclerView.adapter = MyAdapter(codeList) |
| | | |
| | | // 初始化适配器 |
| | | adapter = MyAdapter() |
| | | recyclerView.adapter = adapter |
| | | |
| | | // 观察 LiveData 数据 |
| | | homeViewModel.codeList.observe(this) { codeList -> |
| | | // 如果 codeList 为 null,避免闪退 |
| | | if (codeList != null) { |
| | | adapter.submitList(codeList) |
| | | // 滚动到顶部 |
| | | recyclerView.scrollToPosition(0) |
| | | override fun onResume() { |
| | | super.onResume() |
| | | |
| | | // 每次恢复活动时检查短信权限 |
| | | checkAndHandleSmsPermissions(showDialog = false) |
| | | } |
| | | |
| | | // 权限请求主流程 |
| | | private fun startPermissionsFlow() { |
| | | // 先请求通知权限,再请求短信权限 |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| | | // Android 13+ 需要通知权限 |
| | | if (ContextCompat.checkSelfPermission( |
| | | this, |
| | | Manifest.permission.POST_NOTIFICATIONS |
| | | ) != PackageManager.PERMISSION_GRANTED |
| | | ) { |
| | | notificationPermissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) |
| | | } else { |
| | | // 如果数据为空,可以显示空列表或其他处理 |
| | | Toast.makeText(this, "No data available", Toast.LENGTH_SHORT).show() |
| | | // 已有通知权限,直接请求短信权限 |
| | | requestSmsPermissions() |
| | | } |
| | | } else { |
| | | // 低版本Android无需请求通知权限,直接请求短信权限 |
| | | requestSmsPermissions() |
| | | } |
| | | } |
| | | |
| | | // 请求短信权限 |
| | | private fun requestSmsPermissions() { |
| | | if (!hasSmsPermissions()) { |
| | | // 没有短信权限,请求权限 |
| | | smsPermissionRequest.launch( |
| | | arrayOf( |
| | | Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS |
| | | ) |
| | | ) |
| | | } else { |
| | | // 已有短信权限,直接初始化短信处理 |
| | | registerSmsReceiver() |
| | | // 在Android O及以上版本同步最近短信 |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| | | syncRecentSms() |
| | | } |
| | | } |
| | | |
| | | // 注册广播接收器来监听数据更新 |
| | | val filter = IntentFilter("com.example.firstapp.DATA_UPDATED") |
| | | registerReceiver(object : BroadcastReceiver() { |
| | | override fun onReceive(context: Context, intent: Intent) { |
| | | // 数据已更新,刷新 LiveData |
| | | homeViewModel.loadData() |
| | | } |
| | | |
| | | // 检查并处理短信权限(可选是否显示对话框) |
| | | private fun checkAndHandleSmsPermissions(showDialog: Boolean = true) { |
| | | if (!hasSmsPermissions()) { |
| | | if (showDialog) { |
| | | showSmsPermissionExplanationDialog() |
| | | } |
| | | }, filter) |
| | | |
| | | } else { |
| | | // 已有短信权限,确保接收器已注册 |
| | | if (smsReceiver == null) { |
| | | registerSmsReceiver() |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 检查是否有短信权限 |
| | | private fun hasSmsPermissions(): Boolean { |
| | | return ContextCompat.checkSelfPermission( |
| | | this, Manifest.permission.RECEIVE_SMS |
| | | ) == PackageManager.PERMISSION_GRANTED && |
| | | ContextCompat.checkSelfPermission( |
| | | this, Manifest.permission.READ_SMS |
| | | ) == PackageManager.PERMISSION_GRANTED |
| | | } |
| | | |
| | | // 显示短信权限解释对话框 |
| | | private fun showSmsPermissionExplanationDialog() { |
| | | AlertDialog.Builder(this) |
| | | .setTitle("需要短信权限") |
| | | .setMessage("应用需要短信权限来自动处理和分类您的短信信息,包括快递、付款等内容。没有此权限,应用核心功能将无法正常工作。") |
| | | .setPositiveButton("授予权限") { _, _ -> |
| | | requestSmsPermissions() |
| | | } |
| | | .setNegativeButton("暂时不要") { dialog, _ -> |
| | | dialog.dismiss() |
| | | Toast.makeText(this, "您可以稍后在设置中开启短信权限", Toast.LENGTH_LONG).show() |
| | | } |
| | | .setCancelable(false) |
| | | .show() |
| | | } |
| | | |
| | | // 提供给外部调用的请求通知权限方法 |
| | | fun requestNotificationPermission() { |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| | | // 判断是否已经有通知权限 |
| | | if (ContextCompat.checkSelfPermission( |
| | | this, |
| | | Manifest.permission.POST_NOTIFICATIONS |
| | | ) != PackageManager.PERMISSION_GRANTED |
| | | ) { |
| | | notificationPermissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) |
| | | } else { |
| | | Toast.makeText(this, "已经拥有通知权限", Toast.LENGTH_SHORT).show() |
| | | } |
| | | } else { |
| | | // Android 13以下的版本不需要请求权限 |
| | | Toast.makeText(this, "当前系统版本无需单独请求通知权限", Toast.LENGTH_SHORT).show() |
| | | } |
| | | } |
| | | |
| | | private fun registerSmsReceiver() { |
| | | // 确保不重复注册 |
| | | if (smsReceiver != null) return |
| | | |
| | | Log.d("SMS_DEBUG", "MainActivity注册短信接收器") |
| | | smsReceiver = SmsReceiver() |
| | | val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) |
| | | registerReceiver(smsReceiver, filter) |
| | | } |
| | | |
| | | private fun setupViews() { |
| | | // 获取并显示当前登录的手机号 |
| | | getSharedPreferences("user_info", Context.MODE_PRIVATE).getString("phone", "") ?: "" |
| | | } |
| | | |
| | | private fun logout() { |
| | | // 清除登录信息 |
| | | getSharedPreferences("user_info", Context.MODE_PRIVATE).edit().clear().apply() |
| | | |
| | | // 跳转回登录页 |
| | | startActivity(Intent(this, LoginActivity::class.java)) |
| | | finish() |
| | | } |
| | | |
| | | // 初始化禁用词 |
| | | @RequiresApi(Build.VERSION_CODES.O) |
| | | private fun initializeSecurityKeywords() { |
| | | CoroutineScope(Dispatchers.IO).launch { |
| | | try { |
| | | val response = RetrofitClient.apiService.getSecurityList() |
| | | if (response.code == 200) { |
| | | securityKeywordsList = response.data.map { it.keyword } |
| | | android.util.Log.d( |
| | | "MainActivity", "securityKeywordsList: $securityKeywordsList" |
| | | ) |
| | | // 确保在主线程中调用 syncRecentSms |
| | | runOnUiThread { |
| | | syncRecentSms() |
| | | } |
| | | } else { |
| | | android.util.Log.e( |
| | | "MainActivity", "Failed to get security list: ${response.code}" |
| | | ) |
| | | } |
| | | } catch (e: Exception) { |
| | | android.util.Log.e("MainActivity", "Error fetching security list", e) |
| | | } |
| | | } |
| | | } |
| | | |
| | | @RequiresApi(Build.VERSION_CODES.O) |
| | | private fun syncRecentSms() { |
| | | try { |
| | | val calendar = Calendar.getInstance() |
| | | calendar.add(Calendar.DAY_OF_YEAR, -3) |
| | | val threeDaysAgo = calendar.timeInMillis |
| | | |
| | | val cursor = contentResolver.query( |
| | | Uri.parse("content://sms/inbox"), |
| | | arrayOf("address", "body", "date"), |
| | | "date >= ?", |
| | | arrayOf(threeDaysAgo.toString()), |
| | | "date DESC" |
| | | ) |
| | | |
| | | cursor?.use { |
| | | while (cursor.moveToNext()) { |
| | | val messageBody = cursor.getString(cursor.getColumnIndexOrThrow("body")) |
| | | val datetime = cursor.getLong(cursor.getColumnIndexOrThrow("date")) |
| | | |
| | | // 转换为 Date 对象 |
| | | val date = Date(datetime) |
| | | val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) |
| | | val dateString = sdf.format(date) |
| | | |
| | | // 保存原始短信 |
| | | val msg = Msg(0, "1111", "111111", messageBody, 1, "111", 1, 1) |
| | | val msgId = Core.msg.insert(msg) |
| | | |
| | | // 使用协程处理API调用和数据库操作 |
| | | CoroutineScope(Dispatchers.IO).launch { |
| | | try { |
| | | val response = |
| | | RetrofitModelClient.modelService.processSms(mapOf("content" to messageBody)) |
| | | |
| | | synchronized(syncLock) { |
| | | if (response.status == "success") { |
| | | when (response.data.category) { |
| | | "快递" -> { |
| | | val code = CodeUtils.createExpressCode( |
| | | msgId = msgId, |
| | | createTime = dateString, |
| | | post = response.data.details.post, |
| | | company = response.data.details.company, |
| | | pickupCode = response.data.details.pickupCode, |
| | | address = response.data.details.address, |
| | | time = response.data.details.time |
| | | ) |
| | | CodeUtils.saveCode(code) |
| | | } |
| | | |
| | | "还款" -> { |
| | | val code = CodeUtils.createRepaymentCode( |
| | | msgId = msgId, |
| | | createTime = dateString, |
| | | type = response.data.details.type, |
| | | bank = response.data.details.bank, |
| | | amount = response.data.details.amount, |
| | | date = response.data.details.date, |
| | | address = response.data.details.address, |
| | | minAmount = response.data.details.min_amount, |
| | | number = response.data.details.number |
| | | ) |
| | | CodeUtils.saveCode(code) |
| | | } |
| | | |
| | | "收入" -> { |
| | | val code = CodeUtils.createIncomeCode( |
| | | msgId = msgId, |
| | | createTime = dateString, |
| | | bank = response.data.details.bank, |
| | | amount = response.data.details.amount, |
| | | datetime = response.data.details.datetime, |
| | | address = response.data.details.address, |
| | | balance = response.data.details.balance |
| | | ) |
| | | CodeUtils.saveCode(code) |
| | | } |
| | | |
| | | "航班" -> { |
| | | val code = CodeUtils.createFlightCode( |
| | | msgId = msgId, |
| | | createTime = dateString, |
| | | company = response.data.details.company, |
| | | start = response.data.details.start, |
| | | end = response.data.details.end, |
| | | seat = response.data.details.seat, |
| | | time = response.data.details.time, |
| | | address = response.data.details.address |
| | | ) |
| | | CodeUtils.saveCode(code) |
| | | } |
| | | |
| | | "火车票" -> { |
| | | val code = CodeUtils.createTrainTicketCode( |
| | | msgId = msgId, |
| | | createTime = dateString, |
| | | company = response.data.details.company, |
| | | seat = response.data.details.seat, |
| | | time = response.data.details.time, |
| | | address = response.data.details.address, |
| | | trips = response.data.details.trips |
| | | ) |
| | | CodeUtils.saveCode(code) |
| | | } |
| | | } |
| | | |
| | | // 发送广播通知数据已更新 |
| | | val updateIntent = Intent("com.example.firstapp.DATA_UPDATED") |
| | | sendBroadcast(updateIntent) |
| | | } |
| | | } |
| | | } catch (e: Exception) { |
| | | android.util.Log.e("MainActivity", "处理历史短信出错: ${e.message}", e) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | Toast.makeText(this, "同步短信失败:${e.message}", Toast.LENGTH_SHORT).show() |
| | | } |
| | | } |
| | | |
| | | // 重置提醒计划 |
| | | private fun resetReminders() { |
| | | try { |
| | | // 取消所有现有的提醒任务 |
| | | androidx.work.WorkManager.getInstance(this).cancelAllWorkByTag("reminder_worker") |
| | | |
| | | // 清除旧的提醒偏好设置 |
| | | getSharedPreferences("reminder_prefs", Context.MODE_PRIVATE).edit().clear().apply() |
| | | |
| | | // 重新设置提醒任务 |
| | | (application as App).setupReminderWorker() |
| | | |
| | | // 检查是否有错过的提醒 |
| | | com.example.firstapp.service.ReminderWorker.checkMissedReminders(this) |
| | | |
| | | android.util.Log.d("MainActivity", "已重置提醒计划") |
| | | } catch (e: Exception) { |
| | | android.util.Log.e("MainActivity", "重置提醒计划失败: ${e.message}") |
| | | } |
| | | } |
| | | } |