From 114052d1c680601b6a51f4f0f1bf406352b7d964 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Fri, 21 Mar 2025 19:12:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A`=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1-=E5=BF=AB=E6=8D=B7=E6=8C=87=E4=BB=A4-?= =?UTF-8?q?=E8=AD=A6=E6=8A=A5=E6=8F=90=E9=86=92`=E5=8A=A8=E4=BD=9C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0`=E9=97=AA=E7=83=81=E6=89=8B=E6=9C=BA`?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=20#581?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 5 + .../forwarder/entity/action/AlarmSetting.kt | 2 + .../fragment/action/AlarmFragment.kt | 57 ++++++++- .../forwarder/service/ForegroundService.kt | 23 +++- .../idormy/sms/forwarder/utils/FlashUtils.kt | 116 ++++++++++++++++++ .../layout/fragment_tasks_action_alarm.xml | 111 ++++++++++++++++- app/src/main/res/values-en/strings.xml | 7 +- app/src/main/res/values-zh-rCN/strings.xml | 7 +- app/src/main/res/values-zh-rTW/strings.xml | 8 +- app/src/main/res/values/strings.xml | 7 +- 10 files changed, 332 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/utils/FlashUtils.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 13622162..03566ed6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,10 @@ + + + (), View.OnC binding!!.rgAlarmState.check(R.id.rb_start_alarm) binding!!.layoutAlarmSettings.visibility = View.VISIBLE binding!!.layoutVibrateSettings.visibility = View.VISIBLE + binding!!.layoutFlashSettings.visibility = View.VISIBLE } else { binding!!.rgAlarmState.check(R.id.rb_stop_alarm) binding!!.layoutAlarmSettings.visibility = View.GONE binding!!.layoutVibrateSettings.visibility = View.GONE + binding!!.layoutFlashSettings.visibility = View.GONE } } binding!!.xsbVolume.setDefaultValue(settingVo.volume) @@ -114,8 +117,14 @@ class AlarmFragment : BaseFragment(), View.OnC binding!!.etMusicPath.setText(settingVo.music) binding!!.xsbRepeatTimes.setDefaultValue(if (settingVo.repeatTimes >= 0) settingVo.repeatTimes else 0) binding!!.etVibrationEffect.setText(settingVo.vibrate) + binding!!.xsbFlashTimes.setDefaultValue(if (settingVo.flashTimes >= 0) settingVo.flashTimes else 0) + binding!!.etFlashEffect.setText(settingVo.flash) binding!!.sbEnableMusic.isChecked = settingVo.playTimes >= 0 binding!!.sbEnableVibrate.isChecked = settingVo.repeatTimes >= 0 + binding!!.sbEnableFlash.isChecked = settingVo.flashTimes >= 0 + binding!!.layoutAlarmSettingsContent.visibility = if (settingVo.playTimes >= 0) View.VISIBLE else View.GONE + binding!!.layoutVibrateSettingsContent.visibility = if (settingVo.repeatTimes >= 0) View.VISIBLE else View.GONE + binding!!.layoutFlashSettingsContent.visibility = if (settingVo.flashTimes >= 0) View.VISIBLE else View.GONE } override fun onDestroyView() { @@ -135,13 +144,20 @@ class AlarmFragment : BaseFragment(), View.OnC binding!!.xsbPlayTimes.setOnSeekBarListener { _, _ -> checkSetting(true) } + binding!!.xsbRepeatTimes.setOnSeekBarListener { _, _ -> + checkSetting(true) + } binding!!.rgAlarmState.setOnCheckedChangeListener { _, checkedId -> binding!!.layoutAlarmSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE + binding!!.layoutVibrateSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE + binding!!.layoutFlashSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE checkSetting(true) } binding!!.btInsertVibrationEffect1.setOnClickListener(this) binding!!.btInsertVibrationEffect2.setOnClickListener(this) binding!!.btInsertVibrationEffect3.setOnClickListener(this) + binding!!.btInsertFlashEffect1.setOnClickListener(this) + binding!!.btInsertFlashEffect2.setOnClickListener(this) } @SingleClick @@ -163,6 +179,16 @@ class AlarmFragment : BaseFragment(), View.OnC return } + R.id.bt_insert_flash_effect_1 -> { + CommonUtils.insertOrReplaceText2Cursor(binding!!.etFlashEffect, "X") + return + } + + R.id.bt_insert_flash_effect_2 -> { + CommonUtils.insertOrReplaceText2Cursor(binding!!.etFlashEffect, "O") + return + } + R.id.btn_file_picker -> { // 申请储存权限 XXPermissions.with(this).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(object : OnPermissionCallback { @@ -196,15 +222,20 @@ class AlarmFragment : BaseFragment(), View.OnC } R.id.btn_test -> { + val permissions = arrayListOf() + permissions.add(Permission.WRITE_SETTINGS) + if (binding!!.sbEnableFlash.isChecked) { + permissions.add(Permission.CAMERA) + } // 申请修改系统设置权限 - XXPermissions.with(this).permission(Permission.WRITE_SETTINGS).request(object : OnPermissionCallback { + XXPermissions.with(this).permission(permissions).request(object : OnPermissionCallback { @SuppressLint("SetTextI18n") override fun onGranted(permissions: List, all: Boolean) { mCountDownHelper?.start() try { val settingVo = checkSetting() Log.d(TAG, settingVo.toString()) - if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0) { + if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0 && settingVo.flashTimes < 0) { XToastUtils.error(getString(R.string.alarm_settings_error)) return } @@ -242,12 +273,17 @@ class AlarmFragment : BaseFragment(), View.OnC } R.id.btn_save -> { + val permissions = arrayListOf() + permissions.add(Permission.WRITE_SETTINGS) + if (binding!!.sbEnableFlash.isChecked) { + permissions.add(Permission.CAMERA) + } // 申请修改系统设置权限 - XXPermissions.with(this).permission(Permission.WRITE_SETTINGS).request(object : OnPermissionCallback { + XXPermissions.with(this).permission(permissions).request(object : OnPermissionCallback { @SuppressLint("SetTextI18n") override fun onGranted(permissions: List, all: Boolean) { val settingVo = checkSetting() - if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0) { + if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0 && settingVo.flashTimes < 0) { XToastUtils.error(getString(R.string.alarm_settings_error)) return } @@ -285,11 +321,14 @@ class AlarmFragment : BaseFragment(), View.OnC private fun checkSetting(updateView: Boolean = false): AlarmSetting { val enableMusic = binding!!.sbEnableMusic.isChecked val enableVibrate = binding!!.sbEnableVibrate.isChecked + val enableFlash = binding!!.sbEnableFlash.isChecked val volume = binding!!.xsbVolume.selectedNumber var playTimes = binding!!.xsbPlayTimes.selectedNumber val music = binding!!.etMusicPath.text.toString().trim() var repeatTimes = binding!!.xsbRepeatTimes.selectedNumber val vibrationEffect = binding!!.etVibrationEffect.text.toString().trim() + var flashTimes = binding!!.xsbFlashTimes.selectedNumber + var flashEffect = binding!!.etFlashEffect.text.toString().trim() val description = StringBuilder() val action = if (binding!!.rgAlarmState.checkedRadioButtonId == R.id.rb_start_alarm) { description.append(getString(R.string.start_alarm)) @@ -309,6 +348,14 @@ class AlarmFragment : BaseFragment(), View.OnC } else { repeatTimes = -1 } + if (enableFlash) { + flashEffect.ifEmpty { "XXOOXXOO".also { binding!!.etFlashEffect.setText(it) } } + flashEffect = flashEffect.toUpperCase(Locale.ROOT).replace("1", "X").replace("0", "O") + description.append(", ").append(getString(R.string.alarm_flash_effect)).append(":").append(flashEffect) + description.append(", ").append(getString(R.string.alarm_repeat_times)).append(":").append(flashTimes) + } else { + flashTimes = -1 + } "start" } else { description.append(getString(R.string.stop_alarm)) @@ -319,7 +366,7 @@ class AlarmFragment : BaseFragment(), View.OnC binding!!.tvDescription.text = description.toString() } - return AlarmSetting(description.toString(), action, volume, playTimes, music, repeatTimes, vibrationEffect) + return AlarmSetting(description.toString(), action, volume, playTimes, music, repeatTimes, vibrationEffect, flashTimes, flashEffect) } private fun findAudioFiles(directoryPath: String): List { diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt index e78f7a5f..9947e2a4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt @@ -36,6 +36,7 @@ import com.idormy.sms.forwarder.utils.EXTRA_UPDATE_NOTIFICATION import com.idormy.sms.forwarder.utils.FRONT_CHANNEL_ID import com.idormy.sms.forwarder.utils.FRONT_CHANNEL_NAME import com.idormy.sms.forwarder.utils.FRONT_NOTIFY_ID +import com.idormy.sms.forwarder.utils.FlashUtils import com.idormy.sms.forwarder.utils.INTENT_FRPC_APPLY_FILE import com.idormy.sms.forwarder.utils.Log import com.idormy.sms.forwarder.utils.SettingUtils @@ -97,6 +98,10 @@ class ForegroundService : Service() { private lateinit var vibrationUtils: VibrationUtils private var isVibrating = false + // 闪光灯控制 + private lateinit var flashUtils: FlashUtils + private var isFlash = false + // 音乐播放器 private var alarmPlayer: MediaPlayer? = null private var alarmPlayTimes = 0 @@ -106,6 +111,10 @@ class ForegroundService : Service() { if (vibrationUtils.isVibrating) { vibrationUtils.stopVibration() } + //停止闪光灯 + if (flashUtils.isFlashing) { + flashUtils.stopFlashing() + } //停止播放音乐 alarmPlayer?.release() alarmPlayer = null @@ -182,6 +191,11 @@ class ForegroundService : Service() { isVibrating = true vibrationUtils.startVibration(alarm.vibrate, alarm.repeatTimes) } + //闪光灯提醒 + if (alarm.flashTimes >= 0) { + isFlash = true + flashUtils.startFlashing(alarm.flash, alarm.flashTimes) + } } } @@ -200,6 +214,9 @@ class ForegroundService : Service() { //初始化振动 vibrationUtils = VibrationUtils(this) + + //初始化闪光灯 + flashUtils = FlashUtils(this) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -235,7 +252,7 @@ class ForegroundService : Service() { override fun onDestroy() { //非纯客户端模式 if (!SettingUtils.enablePureClientMode) stopForegroundService() - + flashUtils.release() super.onDestroy() } @@ -319,6 +336,10 @@ class ForegroundService : Service() { if (vibrationUtils.isVibrating) { vibrationUtils.stopVibration() } + //停止闪光灯 + if (flashUtils.isFlashing) { + flashUtils.stopFlashing() + } } catch (e: Exception) { handleException(e, "stopForegroundService") } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/FlashUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/FlashUtils.kt new file mode 100644 index 00000000..b0e1bf44 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/FlashUtils.kt @@ -0,0 +1,116 @@ +@file:Suppress("DEPRECATION") + +package com.idormy.sms.forwarder.utils + +import android.content.Context +import android.hardware.Camera +import android.hardware.camera2.CameraAccessException +import android.hardware.camera2.CameraManager +import android.os.Build +import android.os.Handler +import android.os.Looper + +class FlashUtils(context: Context) { + private var cameraManager: CameraManager? = null + private var cameraId: String? = null + private var legacyCamera: Camera? = null + private var legacyParams: Camera.Parameters? = null + private var handler: Handler? = null + private val duration = 100L // 闪烁持续时间 + var isFlashing = false + private set + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager + try { + cameraId = cameraManager?.cameraIdList?.get(0) // 获取后置摄像头 ID + } catch (e: CameraAccessException) { + e.printStackTrace() + } + } else { + try { + legacyCamera = Camera.open() + legacyParams = legacyCamera?.parameters + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + /** + * 按照模式控制闪光灯 + * @param pattern 例如 "XXOOXXOO" (X-开,O-关) + * @param repeatTimes 闪烁的重复次数,0 表示无限循环 + */ + fun startFlashing(pattern: String, repeatTimes: Int) { + if (isFlashing) return + isFlashing = true + handler = Handler(Looper.getMainLooper()) + + val sequence = pattern.toCharArray() + var index = 0 + var repeatCount = 0 + + val runnable = object : Runnable { + override fun run() { + if (!isFlashing) return + + val shouldFlash = sequence[index] == 'X' || sequence[index] == '1' + setFlashlight(shouldFlash) + index++ + + if (index >= sequence.size) { + index = 0 + repeatCount++ + if (repeatTimes != 0 && repeatCount >= repeatTimes) { + stopFlashing() + return + } + } + + handler?.postDelayed(this, duration) + } + } + + handler?.post(runnable) + } + + /** + * 关闭闪光灯并停止模式 + */ + fun stopFlashing() { + isFlashing = false + handler?.removeCallbacksAndMessages(null) + setFlashlight(false) // 确保停止后灯是关闭的 + } + + /** + * 设置闪光灯状态,兼容 Android 4.4+ + */ + private fun setFlashlight(enable: Boolean) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + cameraManager?.setTorchMode(cameraId!!, enable) + } else { + legacyParams?.flashMode = if (enable) Camera.Parameters.FLASH_MODE_TORCH else Camera.Parameters.FLASH_MODE_OFF + legacyCamera?.parameters = legacyParams + if (enable) legacyCamera?.startPreview() else legacyCamera?.stopPreview() + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * 释放旧 API 资源 + */ + fun release() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + legacyCamera?.stopPreview() + legacyCamera?.release() + legacyCamera = null + } + } + +} diff --git a/app/src/main/res/layout/fragment_tasks_action_alarm.xml b/app/src/main/res/layout/fragment_tasks_action_alarm.xml index be334a94..d309c30e 100644 --- a/app/src/main/res/layout/fragment_tasks_action_alarm.xml +++ b/app/src/main/res/layout/fragment_tasks_action_alarm.xml @@ -209,7 +209,6 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index ddeea823..2b56ca19 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1165,7 +1165,12 @@ Strong vibration Weak vibration No vibration - At least one of Play Music/Vibrate Phone must be enabled + Flash Phone + Flash Effect + Syntax: 1 or X [turn on flash], 0 or O [turn off flash], each time 100ms + Turn on flash 100ms + Turn off flash 100ms + At least one of Play Music/Vibrate Phone/Flash Phone must be enabled %s tag is invalid: %s Please input task name. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 35f61c93..76e52148 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1166,7 +1166,12 @@ 强振动 100ms 弱振动 100ms 不振动 100ms - 播放音乐/振动手机必须至少开启一个 + 闪烁手机 + 闪光效果 + 语法:1或X[开启闪光灯], 0或O[关闭闪光灯], 每次100ms + 开启闪光灯 100ms + 关闭闪光灯 100ms + 播放音乐/振动手机/闪烁手机必须至少开启一个 %s 标签无效:%s 请输入任务名称 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f7ef6dca..2f6223fb 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1166,7 +1166,13 @@ 強振動 100ms 弱振動 100ms 不振動 100ms - 播放音樂/振動手機必須至少開啟一個 + 閃爍手機 + 閃光效果 + 語法:1或X[開啟閃光燈],0或O[關閉閃光燈],每次300毫秒 + 開啟閃光燈 300毫秒 + 關閉閃光燈 300毫秒 + 播放音樂/振動手機/閃爍手機必須至少開啟一個 + %s 標籤無效:%s 請輸入任務名稱 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dab020e5..edb6b194 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1195,7 +1195,12 @@ 强振动 100ms 弱振动 100ms 不振动 100ms - 播放音乐/振动手机必须至少开启一个 + 闪烁手机 + 闪光效果 + 语法:1或X[开启闪光灯], 0或O[关闭闪光灯], 每次100ms + 开启闪光灯 100ms + 关闭闪光灯 100ms + 播放音乐/振动手机/闪烁手机必须至少开启一个 %s 标签无效:%s 请输入任务名称