Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class NotificationMonitorService : NotificationListenerService() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var listenerConnected = false

// 通知转移去重:相同标题+内容在短时间窗口内只转发一次,避免系统重复投递刷屏
private var lastTransferKey: String? = null
private var lastTransferTime = 0L

/**
* 有可用的并且和通知管理器连接成功时回调
*/
Expand Down Expand Up @@ -93,6 +97,9 @@ class NotificationMonitorService : NotificationListenerService() {
// 保存指定包名的通知,其他的一律不保存
saveTargetNotice(pkg, targetApp, title, notice)

// 通知转移:开启后将目标打卡 App 的通知经现有消息渠道转发到目标手机
forwardNotificationIfEnabled(pkg, targetApp, title, notice)

// 截屏模式选中 + 钉钉手动打卡 → 通知被第 99 行拦截,仅测试场景会出现
// 目标应用打卡通知,如果设置通知监听,那么结果来源只能选通知监听。
if (SaveKeyValues.loadInt(Constant.RESULT_SOURCE_KEY, Constant.DEFAULT_INDEX) == 0) {
Expand Down Expand Up @@ -128,6 +135,34 @@ class NotificationMonitorService : NotificationListenerService() {
}
}

/**
* 通知转移:开启后,将目标打卡 App 的通知原文经现有消息渠道转发到目标手机。
* 范围限制为当前目标打卡 App(飞书/企微/钉钉/M3/自定义),不会转发辅助聊天 App 或本应用自身。
* 复用 [MessageDispatcher] 按用户已配置渠道(邮件/企业微信)自动分流,与打卡结果通知同一套通道。
*/
private fun forwardNotificationIfEnabled(pkg: String, targetApp: String, title: String, notice: String) {
if (!SaveKeyValues.loadBoolean(Constant.NOTIFICATION_TRANSFER_KEY, false)) return
if (pkg != targetApp) return
if (notice.isBlank()) return

// 轻量去重:相同标题+内容在 10s 内只转发一次
val key = "$title|$notice"
val now = System.currentTimeMillis()
if (key == lastTransferKey && now - lastTransferTime < 10_000) return
lastTransferKey = key
lastTransferTime = now

MessageDispatcher.sendMessage(title.ifBlank { "通知转移" }, notice)
}

/**
* 校验通知转移依赖的消息渠道是否已配置,未配置时返回告警文案(仍允许保存开关态)。
*/
private fun validateTransferConfig(): String {
val channel = SaveKeyValues.loadInt(Constant.MSG_CHANNEL_KEY, Constant.DEFAULT_INDEX)
return if (channel !in 0..1) "消息渠道未配置,转发可能失败" else ""
}

/**
* 处理远程指令
*/
Expand Down Expand Up @@ -204,6 +239,20 @@ class NotificationMonitorService : NotificationListenerService() {
}
}

notice.contains("开启转移") -> {
SaveKeyValues.saveBoolean(Constant.NOTIFICATION_TRANSFER_KEY, true)
val warning = validateTransferConfig()
MessageDispatcher.sendMessage(
"通知转移状态通知",
"通知转移已开启" + if (warning.isBlank()) "" else "\n⚠️$warning"
)
}

notice.contains("关闭转移") -> {
SaveKeyValues.saveBoolean(Constant.NOTIFICATION_TRANSFER_KEY, false)
MessageDispatcher.sendMessage("通知转移状态通知", "通知转移已关闭")
}

else -> {
// 自定义打卡指令,用户可配置关键词(如 "打卡"),同样需要 DT# 前缀
val key = SaveKeyValues.loadString(Constant.REMOTE_COMMAND_KEY, "打卡")
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/com/pengxh/daily/app/ui/CommandActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ class CommandActivity : KotlinBaseActivity<ActivityCommandBinding>() {
Triple("DT#考勤记录", "导出当天考勤记录", true),
Triple("DT#打卡", "触发一次打卡(默认指令内容为「打卡」)", true),
Triple("DT#状态查询", "查询当前状态(任务、服务、监听、电量、版本、日期等)", true),
Triple("DT#截屏", "截取目标应用屏幕并通过消息渠道返回给用户", true)
Triple("DT#截屏", "截取目标应用屏幕并通过消息渠道返回给用户", true),
Triple("DT#开启转移", "开启通知转移(将打卡应用通知转发到目标手机)", true),
Triple("DT#关闭转移", "关闭通知转移", true)
)
private val clipboard by lazy { getSystemService(ClipboardManager::class.java) }

Expand Down
23 changes: 23 additions & 0 deletions app/src/main/java/com/pengxh/daily/app/ui/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,27 @@ class SettingsActivity : KotlinBaseActivity<ActivitySettingsBinding>() {
SaveKeyValues.saveBoolean(Constant.POWER_SAVE_MODE_KEY, isChecked)
}

binding.transferSwitch.setOnCheckedChangeListener { _, isChecked ->
if (syncingSwitchState) {
return@setOnCheckedChangeListener
}
if (isChecked) {
// 通知转移依赖通知监听服务才能收到目标 App 通知
if (!notificationEnable()) {
"请先开启通知监听".show(this)
binding.transferSwitch.isChecked = false
return@setOnCheckedChangeListener
}
SaveKeyValues.saveBoolean(Constant.NOTIFICATION_TRANSFER_KEY, true)
val channel = SaveKeyValues.loadInt(Constant.MSG_CHANNEL_KEY, Constant.DEFAULT_INDEX)
if (channel !in 0..1) {
"未配置消息渠道,转发可能失败".show(this)
}
} else {
SaveKeyValues.saveBoolean(Constant.NOTIFICATION_TRANSFER_KEY, false)
}
}

binding.introduceLayout.setOnClickListener {
navigatePageTo<QuestionAndAnswerActivity>()
}
Expand Down Expand Up @@ -484,6 +505,8 @@ class SettingsActivity : KotlinBaseActivity<ActivitySettingsBinding>() {
SaveKeyValues.loadBoolean(Constant.BACK_TO_HOME_KEY, false)
binding.powerSaveSwitch.isChecked =
SaveKeyValues.loadBoolean(Constant.POWER_SAVE_MODE_KEY, false)
binding.transferSwitch.isChecked =
SaveKeyValues.loadBoolean(Constant.NOTIFICATION_TRANSFER_KEY, false)
} finally {
syncingSwitchState = false
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/pengxh/daily/app/utils/Constant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ object Constant {
const val RANDOM_TIME_KEY = "RANDOM_TIME_KEY" // 随机时间(Boolean)
const val SKIP_HOLIDAY_KEY = "SKIP_HOLIDAY_KEY" // 跳过节假日(Boolean)
const val POWER_SAVE_MODE_KEY = "POWER_SAVE_MODE_KEY" // 省电模式(Boolean)
const val NOTIFICATION_TRANSFER_KEY = "NOTIFICATION_TRANSFER_KEY" // 通知转移:将打卡应用通知经消息渠道转发到目标手机(Boolean)

// 不导出的sp缓存
const val LAST_RESET_DATE_KEY = "LAST_RESET_DATE_KEY"
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/res/layout/activity_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,48 @@
android:layout_centerVertical="true" />
</RelativeLayout>

<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginHorizontal="@dimen/dp_16"
android:background="#CCC" />

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/itemLayoutHeight"
android:background="@drawable/bg_solid_layout_white_16"
android:paddingHorizontal="@dimen/dp_16">

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">

<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="通知转移"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/transferTipsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_4"
android:text="打卡应用通知经消息渠道转发到目标手机"
android:textColor="@color/text_default_color"
android:textSize="@dimen/sp_10" />
</LinearLayout>

<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/transferSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
</RelativeLayout>

<View
android:layout_width="match_parent"
android:layout_height="1px"
Expand Down