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
请输入任务名称