app 固定时间节点通知

This commit is contained in:
renhaoting 2025-12-30 14:34:48 +08:00
parent 5e5ba292bd
commit 8f038ca3ea
14 changed files with 624 additions and 58 deletions

View File

@ -227,6 +227,7 @@ class MainActivity : AppViewsActivity<ViewBinding, UiState, ViewModel>(), OnTabS
"Notific_Type" to when (notifyFrom) { "Notific_Type" to when (notifyFrom) {
NotificationCheckController.NotificationType.UNLOCK.string -> 1 NotificationCheckController.NotificationType.UNLOCK.string -> 1
NotificationCheckController.NotificationType.BACKGROUND.string -> 1 NotificationCheckController.NotificationType.BACKGROUND.string -> 1
NotificationCheckController.NotificationType.FIXTIMEPOINT.string -> 1
NotificationCheckController.NotificationType.KEEPALIVE.string -> 1 NotificationCheckController.NotificationType.KEEPALIVE.string -> 1
NotificationCheckController.NotificationType.FCM.string -> 3 NotificationCheckController.NotificationType.FCM.string -> 3
NotificationCheckController.NotificationType.RESIDENT.string -> 4 NotificationCheckController.NotificationType.RESIDENT.string -> 4
@ -256,6 +257,7 @@ class MainActivity : AppViewsActivity<ViewBinding, UiState, ViewModel>(), OnTabS
"Notific_Type" to when (intent.getStringExtra(LANDING_NOTIFICATION_FROM).orEmpty()) { "Notific_Type" to when (intent.getStringExtra(LANDING_NOTIFICATION_FROM).orEmpty()) {
NotificationCheckController.NotificationType.UNLOCK.string -> 1 NotificationCheckController.NotificationType.UNLOCK.string -> 1
NotificationCheckController.NotificationType.BACKGROUND.string -> 1 NotificationCheckController.NotificationType.BACKGROUND.string -> 1
NotificationCheckController.NotificationType.FIXTIMEPOINT.string -> 1
NotificationCheckController.NotificationType.KEEPALIVE.string -> 1 NotificationCheckController.NotificationType.KEEPALIVE.string -> 1
NotificationCheckController.NotificationType.FCM.string -> 3 NotificationCheckController.NotificationType.FCM.string -> 3
NotificationCheckController.NotificationType.RESIDENT.string -> 4 NotificationCheckController.NotificationType.RESIDENT.string -> 4

View File

@ -4,7 +4,7 @@
"unlock_push_interval": "10", "unlock_push_interval": "10",
"background_push_interval": "1", "background_push_interval": "1",
"hover_duration_strategy_switch": 1, "hover_duration_strategy_switch": 1,
"hover_duration_loop_count": 8888888, "hover_duration_loop_count": 88888888,
"new_user_cooldown": 0, "new_user_cooldown": 0,
"do_not_disturb_start": "02:00", "do_not_disturb_start": "02:00",
"do_not_disturb_end": "08:00", "do_not_disturb_end": "08:00",
@ -16,7 +16,7 @@
"unlock_push_interval": "10", "unlock_push_interval": "10",
"background_push_interval": "1", "background_push_interval": "1",
"hover_duration_strategy_switch": 1, "hover_duration_strategy_switch": 1,
"hover_duration_loop_count": 8888888, "hover_duration_loop_count": 88888888,
"new_user_cooldown": "24", "new_user_cooldown": "24",
"do_not_disturb_start": "02:00", "do_not_disturb_start": "02:00",
"do_not_disturb_end": "08:00", "do_not_disturb_end": "08:00",

View File

@ -0,0 +1,8 @@
package com.remax.notification.beans
import com.ama.core.architecture.util.DateUtil
data class FixTimeRecord (
private val mDayStartTime: Long = DateUtil.getTodayStartTimeMs(),
private val mHasShowTimePointList: MutableList<Int> = mutableListOf()
)

View File

@ -282,6 +282,87 @@ class GeneralModelManager() {
} }
} }
class FixTimeModelManager() {
fun getModel(context: Context,type:NotificationCheckController.NotificationType): GeneralNotificationData {
val notificationId = type2notificationId[NotificationType.GENERAL] ?: 0
val data = PushContentController.getNextPushContent()!!
val title = data.title
val content = data.desc
val pendingIntent = entryPointPendingIntent(context, notificationId) {
it.putExtra(LANDING_NOTIFICATION_ACTION, data.actionType)
it.putExtra(LANDING_NOTIFICATION_FROM, type.string)
it.putExtra(LANDING_NOTIFICATION_TITLE, title)
it.putExtra(LANDING_NOTIFICATION_CONTENT, content)
}
val badgeCount = Random.nextInt(1, 100).toString()
val contentView = NotificationRemoteViewsBuilder(
context.packageName,
R.layout.layout_notification_beauty_12,
R.layout.layout_notification_beauty
)
.setImageViewResource(
R.id.iv, when (data.iconType) {
PushContent.ICON_TYPE_PHOTO -> R.drawable.ic_noti_photo
PushContent.ICON_TYPE_VIDEO -> R.drawable.ic_noti_video
PushContent.ICON_TYPE_DOCUMENT -> R.drawable.ic_noti_document
PushContent.ICON_TYPE_AUDIO -> R.drawable.ic_noti_audio
PushContent.ICON_TYPE_SCREENSHOT -> R.drawable.ic_noti_shot
PushContent.ICON_TYPE_RECOVERED -> R.drawable.ic_noti_recover
else -> R.drawable.ic_noti_photo
}
)
.setTextViewText(R.id.tvCount, badgeCount)
.setTextViewText(R.id.tvTitle, title)
.setTextViewText(R.id.tvDesc, content)
.setTextViewText(
R.id.tvAction, when (data.iconType) {
PushContent.ICON_TYPE_SCREENSHOT -> StringUtils.getString(R.string.noti_clean)
else -> StringUtils.getString(R.string.noti_recovery)
}
)
.build()
val bigContentView = NotificationRemoteViewsBuilder(
context.packageName,
R.layout.layout_notification_beauty_12,
R.layout.layout_notification_beauty
)
.setImageViewResource(
R.id.iv, when (data.iconType) {
PushContent.ICON_TYPE_PHOTO -> R.drawable.ic_noti_photo
PushContent.ICON_TYPE_VIDEO -> R.drawable.ic_noti_video
PushContent.ICON_TYPE_DOCUMENT -> R.drawable.ic_noti_document
PushContent.ICON_TYPE_AUDIO -> R.drawable.ic_noti_audio
PushContent.ICON_TYPE_SCREENSHOT -> R.drawable.ic_noti_shot
PushContent.ICON_TYPE_RECOVERED -> R.drawable.ic_noti_recover
else -> R.drawable.ic_noti_photo
}
)
.setTextViewText(R.id.tvCount, badgeCount)
.setTextViewText(R.id.tvTitle, title)
.setTextViewText(R.id.tvDesc, content)
.setTextViewText(
R.id.tvAction, when (data.iconType) {
PushContent.ICON_TYPE_SCREENSHOT -> StringUtils.getString(R.string.noti_clean)
else -> StringUtils.getString(R.string.noti_recovery)
}
)
.build()
return GeneralNotificationData(
notificationId = notificationId,
contentTitle = title,
contentContent = content,
contentIntent = pendingIntent,
contentView = contentView,
bigContentView = bigContentView
)
}
}
class ResidentModelManger { class ResidentModelManger {
fun getModel(context: Context): GeneralNotificationData { fun getModel(context: Context): GeneralNotificationData {

View File

@ -47,7 +47,8 @@ class NotificationCheckController private constructor() {
*/ */
enum class NotificationType(val string: String) { enum class NotificationType(val string: String) {
UNLOCK("local_push"), // 解锁通知 UNLOCK("local_push"), // 解锁通知
BACKGROUND("local_push"), // 后台通知 BACKGROUND("local_push"), // app后台 - for withdraw
FIXTIMEPOINT("fix_time_point_push"), // 每日固定时间点 "9:10""12:10""13:10""19:10""21:00""22:30""23:30"
KEEPALIVE("local_push"), // 保活通知(无间隔限制) KEEPALIVE("local_push"), // 保活通知(无间隔限制)
FCM("firebase_push"), // FCM 推送通知(无间隔限制) FCM("firebase_push"), // FCM 推送通知(无间隔限制)
RESIDENT("top_notification") // 常驻(无间隔限制) RESIDENT("top_notification") // 常驻(无间隔限制)

View File

@ -13,25 +13,32 @@ import android.os.Looper
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.ama.core.architecture.util.DateUtil
import com.ama.core.architecture.util.SpUtil
import com.remax.base.ext.canSendNotification import com.remax.base.ext.canSendNotification
import com.remax.base.report.DataReportManager import com.remax.base.report.DataReportManager
import com.remax.notification.NotifyConst import com.remax.notification.NotifyConst
import com.remax.notification.R import com.remax.notification.R
import com.remax.notification.builder.FixTimeModelManager
import com.remax.notification.builder.GeneralModelManager import com.remax.notification.builder.GeneralModelManager
import com.remax.notification.builder.GeneralNotificationData import com.remax.notification.builder.GeneralNotificationData
import com.remax.notification.builder.NotificationType import com.remax.notification.builder.NotificationType
import com.remax.notification.builder.ResidentModelManger import com.remax.notification.builder.ResidentModelManger
import com.remax.notification.builder.type2notificationId import com.remax.notification.builder.type2notificationId
import com.remax.notification.config.NotificationConfigController
import com.remax.notification.check.NotificationCheckController import com.remax.notification.check.NotificationCheckController
import com.remax.notification.utils.NotiLogger import com.remax.notification.config.NotificationConfigController
import com.remax.notification.newUtil.NotificationRecorder
import com.remax.notification.receiver.NotificationDeleteReceiver import com.remax.notification.receiver.NotificationDeleteReceiver
import com.remax.notification.timing.NotificationTimingController
import com.remax.notification.utils.NotiLogger
import com.remax.notification.utils.TimeCheckUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
/** /**
* 通知触发控制器 * 通知触发控制器
* 提供常驻通知和普通通知的触发功能 * 提供常驻通知和普通通知的触发功能
@ -42,10 +49,13 @@ object NotificationTriggerController {
const val CHANNEL_ID_RESIDENT = "resident_notification" const val CHANNEL_ID_RESIDENT = "resident_notification"
const val CHANNEL_ID_GENERAL = "general_notification" const val CHANNEL_ID_GENERAL = "general_notification"
const val CHANNEL_ID_GENERAL_SILENT = "general_silent_notification" const val CHANNEL_ID_GENERAL_SILENT = "general_silent_notification"
const val CHANNEL_ID_BEAUTY = "general_beauty"
const val CHANNEL_NAME_RESIDENT = "recovery_resident" const val CHANNEL_NAME_RESIDENT = "recovery_resident"
const val CHANNEL_NAME_GENERAL = "recovery_single" const val CHANNEL_NAME_GENERAL = "recovery_single"
const val CHANNEL_NAME_GENERAL_SILENT = "recovery_loop" const val CHANNEL_NAME_GENERAL_SILENT = "recovery_loop"
const val CHANNEL_NAME_BEAUTY = "beauty_single"
private var notificationManager: NotificationManagerCompat? = null private var notificationManager: NotificationManagerCompat? = null
private var context: Context? = null private var context: Context? = null
@ -58,10 +68,14 @@ object NotificationTriggerController {
private var repeatCount: Int = 0 private var repeatCount: Int = 0
private var currentNotificationId: Int = 0 private var currentNotificationId: Int = 0
private var mFixTimeHandler: Handler? = null
private var mFixTimeRunnable: Runnable? = null
/** /**
* 初始化通知通道 * 初始化通知通道
* @param context 上下文 * @param context 上下文
*/ */
@SuppressLint("NewApi")
fun initializeChannels(context: Context) { fun initializeChannels(context: Context) {
this.context = context this.context = context
this.notificationManager = NotificationManagerCompat.from(context) this.notificationManager = NotificationManagerCompat.from(context)
@ -86,6 +100,16 @@ object NotificationTriggerController {
enableVibration(false) enableVibration(false)
} }
// beauty
val beautyChannel = NotificationChannel(
CHANNEL_ID_BEAUTY, CHANNEL_NAME_BEAUTY, NotificationManager.IMPORTANCE_HIGH
).apply {
description = "for beauty notification"
setShowBadge(true)
enableLights(false)
enableVibration(false)
}
// 静音通知通道 // 静音通知通道
val silentChannel = NotificationChannel( val silentChannel = NotificationChannel(
CHANNEL_ID_GENERAL_SILENT, CHANNEL_ID_GENERAL_SILENT,
@ -101,7 +125,7 @@ object NotificationTriggerController {
notificationManager?.createNotificationChannels( notificationManager?.createNotificationChannels(
listOf( listOf(
residentChannel, generalChannel, silentChannel residentChannel, generalChannel, silentChannel, beautyChannel
) )
) )
NotiLogger.d("通知通道创建完成") NotiLogger.d("通知通道创建完成")
@ -207,6 +231,7 @@ object NotificationTriggerController {
// 创建 Handler // 创建 Handler
repeatHandler = Handler(Looper.getMainLooper()) repeatHandler = Handler(Looper.getMainLooper())
// 创建重复任务 // 创建重复任务
repeatRunnable = object : Runnable { repeatRunnable = object : Runnable {
override fun run() { override fun run() {
@ -237,6 +262,30 @@ object NotificationTriggerController {
repeatHandler?.postDelayed(repeatRunnable!!, 4000) repeatHandler?.postDelayed(repeatRunnable!!, 4000)
} }
private fun checkAndStartFixTimeNotification() {
stopFixTimeNotification()
mFixTimeHandler = Handler(Looper.getMainLooper())
mFixTimeRunnable = object : Runnable {
override fun run() {
val lastFixNotifiShowMs = NotificationRecorder.getLastWithdrawShowTime()
if (/*TimeCheckUtil.isTargetTime() && */(DateUtil.getCurTimeMs() - lastFixNotifiShowMs > 60000)) {
NotificationTimingController.getInstance().triggerNotificationIfAllowed(NotificationCheckController.NotificationType.FIXTIMEPOINT)
NotificationRecorder.saveLastWithdrawShowTime(DateUtil.getCurTimeMs())
NotiLogger.d("固定时间节点通知,第${repeatCount + 1}")
}
mFixTimeHandler?.postDelayed(this, 1000)
}
}
mFixTimeHandler?.postDelayed(mFixTimeRunnable!!, 1000)
}
/** /**
* 停止重复通知 * 停止重复通知
*/ */
@ -245,11 +294,17 @@ object NotificationTriggerController {
repeatHandler = null repeatHandler = null
repeatRunnable = null repeatRunnable = null
repeatCount = 0 repeatCount = 0
currentNotificationId = 0
NotiLogger.d("停止重复通知") NotiLogger.d("停止重复通知")
} }
fun stopFixTimeNotification() {
mFixTimeHandler?.removeCallbacks(mFixTimeRunnable ?: return)
mFixTimeHandler = null
mFixTimeRunnable = null
NotiLogger.d("停止时间节点通知")
}
fun triggerResidentNotification() { fun triggerResidentNotification() {
if (context?.canSendNotification() == true) { if (context?.canSendNotification() == true) {
triggerNotification( triggerNotification(
@ -274,11 +329,15 @@ object NotificationTriggerController {
onNotificationSent?.invoke() onNotificationSent?.invoke()
generalTrack(type, notificationData) generalTrack(type, notificationData)
// 检查是否需要重复通知 // 检查是否需要重复通知
checkAndStartRepeatNotification(notificationData, notification,type) checkAndStartRepeatNotification(notificationData, notification, type)
}) })
} }
} }
fun triggerFixTimeNotification() {
checkAndStartFixTimeNotification()
}
private fun resident(model: GeneralNotificationData) = private fun resident(model: GeneralNotificationData) =
NotificationCompat.Builder(context!!, CHANNEL_ID_RESIDENT) NotificationCompat.Builder(context!!, CHANNEL_ID_RESIDENT)
.setColor(ContextCompat.getColor(context!!, R.color.noti_color)) .setColor(ContextCompat.getColor(context!!, R.color.noti_color))
@ -330,6 +389,39 @@ object NotificationTriggerController {
}.build() }.build()
} }
private fun fixTime(model: GeneralNotificationData): Notification {
// 根据重复通知配置选择通道
val channelId = CHANNEL_ID_BEAUTY
// 创建删除监听 Intent
val deleteIntent = Intent(context, NotificationDeleteReceiver::class.java).apply {
action = "com.remax.notification.ACTION_NOTIFICATION_DELETE"
}
val deletePendingIntent = PendingIntent.getBroadcast(
context,
System.currentTimeMillis().toInt(),
deleteIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val builder = NotificationCompat.Builder(context!!, channelId)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setColor(ContextCompat.getColor(context!!, R.color.noti_color))
.setSmallIcon(R.drawable.ic_noti_icon).setAutoCancel(true)
.setGroup("push_" + System.currentTimeMillis()).setContentText(model.contentTitle)
.setContentIntent(model.contentIntent).setDeleteIntent(deletePendingIntent) // 设置删除监听
.setCustomContentView(model.contentView).setCustomBigContentView(model.bigContentView)
// 记录使用的通道
NotiLogger.d("使用普通通道发送通知: $channelId")
return builder.apply {
if (Build.VERSION.SDK_INT >= 31) {
setCustomHeadsUpContentView(model.contentView)
}
}.build()
}
/** /**
* 取消通知 * 取消通知
* @param notificationId 通知ID * @param notificationId 通知ID
@ -428,6 +520,30 @@ object NotificationTriggerController {
"Notific_Type" to when (type) { "Notific_Type" to when (type) {
NotificationCheckController.NotificationType.UNLOCK -> 1 NotificationCheckController.NotificationType.UNLOCK -> 1
NotificationCheckController.NotificationType.BACKGROUND -> 1 NotificationCheckController.NotificationType.BACKGROUND -> 1
NotificationCheckController.NotificationType.FIXTIMEPOINT -> 1
NotificationCheckController.NotificationType.KEEPALIVE -> 1
NotificationCheckController.NotificationType.FCM -> 3
else -> 4
},
"Notific_Position" to 1,
"Notific_Priority" to "PRIORITY_MAX",
"event_id" to "customer_general_style",
"title" to notificationData.contentTitle,
"text" to notificationData.contentContent,
)
)
}
private fun fixTimeTrack(
type: NotificationCheckController.NotificationType,
notificationData: GeneralNotificationData
) {
DataReportManager.reportData(
"Notific_Show", mapOf(
"Notific_Type" to when (type) {
NotificationCheckController.NotificationType.UNLOCK -> 1
NotificationCheckController.NotificationType.BACKGROUND -> 1
NotificationCheckController.NotificationType.FIXTIMEPOINT -> 1
NotificationCheckController.NotificationType.KEEPALIVE -> 1 NotificationCheckController.NotificationType.KEEPALIVE -> 1
NotificationCheckController.NotificationType.FCM -> 3 NotificationCheckController.NotificationType.FCM -> 3
else -> 4 else -> 4

View File

@ -1,6 +1,7 @@
package com.remax.notification.newUtil package com.remax.notification.newUtil
import android.app.PendingIntent import android.app.PendingIntent
import com.google.gson.annotations.SerializedName
class NotificationConfig(val notificationId: Int = System.currentTimeMillis().toInt(), class NotificationConfig(val notificationId: Int = System.currentTimeMillis().toInt(),
val channelId: String, val channelId: String,
@ -12,3 +13,4 @@ class NotificationConfig(val notificationId: Int = System.currentTimeMillis().to
val useFullScreenIntent: Boolean = false, val useFullScreenIntent: Boolean = false,
) { ) {
} }

View File

@ -1,10 +1,9 @@
package com.remax.notification.newUtil package com.remax.notification.newUtil
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import com.ama.core.architecture.BaseApp import com.ama.core.architecture.BaseApp
import com.ama.core.architecture.util.AndroidUtil
import com.ama.core.architecture.util.ResUtil import com.ama.core.architecture.util.ResUtil
import com.remax.notification.R import com.remax.notification.R
import com.remax.notification.builder.LANDING_NOTIFICATION_ACTION import com.remax.notification.builder.LANDING_NOTIFICATION_ACTION
@ -17,8 +16,10 @@ import com.remax.notification.config.PushContent
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
object NotificationDatas { object NotificationDatas {
private var mCurRandomIndex = 0
const val GAP_FOR_WITHDRAW_NOTIFY = 30 * 1000 const val GAP_FOR_WITHDRAW_NOTIFY = 30 * 1000
var context: Context = BaseApp.appContext() var context: Context = BaseApp.appContext()
@ -28,37 +29,62 @@ object NotificationDatas {
const val NOTI_TYPE_BG_RANDOM = "NOTI_TYPE_BG_RANDOM" const val NOTI_TYPE_BG_RANDOM = "NOTI_TYPE_BG_RANDOM"
const val NOTI_TYPE_RESIDENT = "NOTI_TYPE_RESIDENT" const val NOTI_TYPE_RESIDENT = "NOTI_TYPE_RESIDENT"
const val NOTI_TYPE_UNLOCK = "NOTI_TYPE_UNLOCK" const val NOTI_TYPE_UNLOCK = "NOTI_TYPE_UNLOCK"
const val NOTI_TYPE_FXITIME = "NOTI_TYPE_FXITIME"
// notifi id // notifi id
const val NOTI_ID_TYPE_BG_WITHDRAW = 666 const val NOTI_ID_TYPE_BG_WITHDRAW = 666
const val NOTI_ID_TYPE_BG_RANDOM = 667 const val NOTI_ID_TYPE_BG_RANDOM = 667
const val NOTI_ID_TYPE_RESIDENT = 668 const val NOTI_ID_TYPE_RESIDENT = 668
const val NOTI_ID_TYPE_UNLOCK = 669 const val NOTI_ID_TYPE_UNLOCK = 669
const val NOTI_ID_TYPE_FXITIME = 670
const val NOTI_ID_TYPE_RANDOM_1 = 670
const val NOTI_ID_TYPE_RANDOM_2 = 671
const val NOTI_ID_TYPE_RANDOM_3 = 672
const val NOTI_ID_TYPE_RANDOM_4 = 673
const val NOTI_ID_TYPE_RANDOM_5 = 674
const val NOTI_ID_TYPE_RANDOM_6 = 675
const val NOTI_ID_TYPE_RANDOM_7 = 676
const val NOTI_ID_TYPE_RANDOM_8 = 677
const val NOTI_ID_TYPE_RANDOM_9 = 678
const val NOTI_ID_TYPE_RANDOM_10 = 679
const val NOTI_ID_TYPE_RANDOM_11 = 680
// channelId type // channelId type
const val CHANNEL_TYPE_BG_WITHDRAW = "CHANNEL_TYPE_BG_WITHDRAW" const val CHANNEL_TYPE_BG_WITHDRAW = "CHANNEL_TYPE_BG_WITHDRAW"
const val CHANNEL_TYPE_BG_RANDOM = "CHANNEL_TYPE_BG_RANDOM" const val CHANNEL_TYPE_BG_RANDOM = "CHANNEL_TYPE_BG_RANDOM"
const val CHANNEL_TYPE_RESIDENT = "CHANNEL_TYPE_RESIDENT" const val CHANNEL_TYPE_RESIDENT = "CHANNEL_TYPE_RESIDENT"
const val CHANNEL_TYPE_UNLOCK = "CHANNEL_TYPE_UNLOCK" const val CHANNEL_TYPE_UNLOCK = "CHANNEL_TYPE_UNLOCK"
const val CHANNEL_TYPE_RANDOM = "CHANNEL_TYPE_RANDOM"
// channelId name // channelId name
const val CHANNEL_TYPE_BG_WITHDRAW_NAME = "CHANNEL_TYPE_BG_WITHDRAW_NAME" const val CHANNEL_TYPE_BG_WITHDRAW_NAME = "CHANNEL_TYPE_BG_WITHDRAW_NAME"
const val CHANNEL_TYPE_BG_RANDOM_NAME = "CHANNEL_TYPE_BG_RANDOM_NAME" const val CHANNEL_TYPE_BG_RANDOM_NAME = "CHANNEL_TYPE_BG_RANDOM_NAME"
const val CHANNEL_TYPE_RESIDENT_NAME = "CHANNEL_TYPE_RESIDENT_NAME" const val CHANNEL_TYPE_RESIDENT_NAME = "CHANNEL_TYPE_RESIDENT_NAME"
const val CHANNEL_TYPE_UNLOCK_NAME = "CHANNEL_TYPE_UNLOCK_NAME" const val CHANNEL_TYPE_UNLOCK_NAME = "CHANNEL_TYPE_UNLOCK_NAME"
const val CHANNEL_TYPE_RANDOM_NAME = "CHANNEL_TYPE_RANDOM_NAME"
private val mConfigList: ConcurrentHashMap<String, NotificationConfig> = ConcurrentHashMap() private val mConfigMap: ConcurrentHashMap<String, NotificationConfig> = ConcurrentHashMap()
private val mRandomConfigList: MutableList<NotificationConfig> = mutableListOf()
init { init {
initRandomConfigs()
// # 切换后台 - 提现
val bgWithdrawIntent = entryPointPendingIntent(context, val bgWithdrawIntent = entryPointPendingIntent(context,
type2notificationId[NotificationType.RESIDENT_WITHDRAW] ?: 0 type2notificationId[NotificationType.RESIDENT_WITHDRAW] ?: 0
) { ) {
it.putExtra(LANDING_NOTIFICATION_ACTION, PushContent.ACTION_TYPE_WITHDRAW) it.putExtra(LANDING_NOTIFICATION_ACTION, PushContent.ACTION_TYPE_WITHDRAW)
it.putExtra(LANDING_NOTIFICATION_FROM, NotificationCheckController.NotificationType.RESIDENT.string) it.putExtra(LANDING_NOTIFICATION_FROM, NotificationCheckController.NotificationType.RESIDENT.string)
} }
mConfigList.put(NOTI_TYPE_BG_WITHDRAW, NotificationConfig( mConfigMap.put(NOTI_TYPE_BG_WITHDRAW, NotificationConfig(
NOTI_ID_TYPE_BG_WITHDRAW, NOTI_ID_TYPE_BG_WITHDRAW,
CHANNEL_TYPE_BG_WITHDRAW, CHANNEL_TYPE_BG_WITHDRAW,
CHANNEL_TYPE_BG_WITHDRAW_NAME, CHANNEL_TYPE_BG_WITHDRAW_NAME,
@ -68,12 +94,144 @@ object NotificationDatas {
bgWithdrawIntent, bgWithdrawIntent,
true, true,
)) ))
} }
private fun initRandomConfigs() {
try {
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_1,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"Time to claim coins",
"You have free coins today!Open to claim!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_1))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_2,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"Time to claim cash",
"You have free cash today!Open to claim!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_2))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_3,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"Hey!",
"You received R$10, click to withdraw immediately!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_3))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_4,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"\uD83D\uDCDE\uD83D\uDCDE\uD83D\uDCDE Do you miss me? ",
"Come play the game and withdraw now!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_4))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_5,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"\uD83D\uDCDE\uD83D\uDCDE\uD83D\uDCDE Do you miss me? ",
"Come play the game and withdraw now!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_5))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_6,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"\uD83D\uDCDE\uD83D\uDCDE\uD83D\uDCDE Do you miss me? ",
"Come play the game and withdraw now!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_6))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_7,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"\uD83D\uDCDE\uD83D\uDCDE\uD83D\uDCDE Do you miss me? ",
"Come play the game and withdraw now!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_7))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_8,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"Todays 1,024th Cash-Out Champion is HERE!",
"Youre the missing piece! Join now!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_8))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_9,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"User #888 Just Cashed Out",
"Dont be left behind! Tap to claim yours!",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_9))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_10,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"You missed(2) calls",
"+86-12345, +86-666999",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_10))
)
mRandomConfigList.add(NotificationConfig(
NOTI_ID_TYPE_RANDOM_11,
CHANNEL_TYPE_RANDOM,
CHANNEL_TYPE_RANDOM_NAME,
"Gmail",
"support.gov@gmail.com",
R.mipmap.task_cash,
entryPointPendingIntent(context, NOTI_ID_TYPE_RANDOM_11))
)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun getConfigForType(notifiType: String): NotificationConfig? { fun getConfigForType(notifiType: String): NotificationConfig? {
return mConfigList[notifiType] return mConfigMap[notifiType]
}
fun getRandomConfig(): NotificationConfig {
var randomIndex = AndroidUtil.randomInt(0, mRandomConfigList.size - 1)
if (mCurRandomIndex == randomIndex) {
randomIndex++
if (randomIndex >= mRandomConfigList.size) {
randomIndex = 0
}
}
mCurRandomIndex = randomIndex
return mRandomConfigList[mCurRandomIndex]
} }
} }

View File

@ -49,7 +49,28 @@ class NotificationUtil private constructor() {
/** /**
* 1. 基本通知 * 1. 基本通知
*/ */
fun showBasicNotification( fun showBasicNotification(notifiConfig: NotificationConfig) {
val notificationId = notifiConfig.notificationId
val channelId = notifiConfig.channelId
val channelName = notifiConfig.channelName
val title = notifiConfig.title
val content = notifiConfig.content
val smallIcon = notifiConfig.smallIcon
val pendingIntent = notifiConfig.intent
val notification = NotificationCompat.Builder(mContext, channelId)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(smallIcon)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
showNotification(notificationId, notification, channelName)
}
fun showBasicNotification_old(
channelId: String, channelId: String,
title: String, title: String,
content: String, content: String,
@ -413,7 +434,7 @@ class NotificationUtil private constructor() {
@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
private fun doShowNotification(notificationId: Int = System.currentTimeMillis().toInt(), notification: Notification, channelName: String) { private fun doShowNotification(notificationId: Int = System.currentTimeMillis().toInt(), notification: Notification, channelName: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationManager.getNotificationChannel(notification.channelId) == null) {
createNotificationChannel(notification.channelId, channelName, NotificationCompat.PRIORITY_HIGH) createNotificationChannel(notification.channelId, channelName, NotificationCompat.PRIORITY_HIGH)
} }
NotificationManagerCompat.from(mContext).notify(notificationId, notification) NotificationManagerCompat.from(mContext).notify(notificationId, notification)

View File

@ -13,6 +13,7 @@ import com.remax.base.ext.canSendNotification
import com.remax.base.report.DataReportManager import com.remax.base.report.DataReportManager
import com.remax.notification.check.NotificationCheckController import com.remax.notification.check.NotificationCheckController
import com.remax.notification.controller.NotificationTriggerController import com.remax.notification.controller.NotificationTriggerController
import com.remax.notification.controller.NotificationTriggerController.triggerFixTimeNotification
import com.remax.notification.timing.NotificationTimingController import com.remax.notification.timing.NotificationTimingController
import com.remax.notification.utils.NotiLogger import com.remax.notification.utils.NotiLogger
import com.remax.notification.utils.Topic import com.remax.notification.utils.Topic
@ -153,6 +154,7 @@ class NotificationKeepAliveService : Service() {
NotiLogger.d("保活服务已在运行中,刷新通知,间隔: ${intervalSeconds}") NotiLogger.d("保活服务已在运行中,刷新通知,间隔: ${intervalSeconds}")
// 服务已运行,只刷新通知 // 服务已运行,只刷新通知
updateForegroundNotification() updateForegroundNotification()
triggerFixTimeNotification()
return return
} }
@ -219,6 +221,7 @@ class NotificationKeepAliveService : Service() {
NotiLogger.d("执行保活任务") NotiLogger.d("执行保活任务")
DataReportManager.reportData("Notific_Pull", mapOf("topic" to "timer")) DataReportManager.reportData("Notific_Pull", mapOf("topic" to "timer"))
updateForegroundNotification() updateForegroundNotification()
triggerFixTimeNotification()
// 尝试触发保活通知 // 尝试触发保活通知
NotificationTimingController.getInstance().triggerNotificationIfAllowed( NotificationTimingController.getInstance().triggerNotificationIfAllowed(
NotificationCheckController.NotificationType.KEEPALIVE NotificationCheckController.NotificationType.KEEPALIVE
@ -237,6 +240,7 @@ class NotificationKeepAliveService : Service() {
// 延迟执行首次任务 // 延迟执行首次任务
handler?.postDelayed(keepAliveRunnable!!, intervalSeconds * 1000) handler?.postDelayed(keepAliveRunnable!!, intervalSeconds * 1000)
triggerFixTimeNotification()
} }
/** /**

View File

@ -1,16 +1,13 @@
package com.remax.notification.timing package com.remax.notification.timing
import android.app.ActivityManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
@ -31,14 +28,10 @@ import com.remax.notification.utils.NotiLogger
import com.remax.notification.utils.FCMTopicManager import com.remax.notification.utils.FCMTopicManager
import com.remax.notification.service.FCMService import com.remax.notification.service.FCMService
import com.remax.notification.service.NotificationKeepAliveServiceManager import com.remax.notification.service.NotificationKeepAliveServiceManager
import com.remax.notification.utils.DateUtil
import com.remax.notification.utils.Topic
import com.remax.notification.worker.NotificationKeepAliveWorker import com.remax.notification.worker.NotificationKeepAliveWorker
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -284,7 +277,8 @@ class NotificationTimingController private constructor() : LifecycleObserver {
NotificationCheckController.NotificationType.BACKGROUND -> "后台通知" NotificationCheckController.NotificationType.BACKGROUND -> "后台通知"
NotificationCheckController.NotificationType.KEEPALIVE -> "保活通知" NotificationCheckController.NotificationType.KEEPALIVE -> "保活通知"
NotificationCheckController.NotificationType.FCM -> "FCM推送通知" NotificationCheckController.NotificationType.FCM -> "FCM推送通知"
NotificationCheckController.NotificationType.RESIDENT -> "" NotificationCheckController.NotificationType.RESIDENT -> "常驻通知"
NotificationCheckController.NotificationType.FIXTIMEPOINT -> "固定时间点通知"
} }
DataReportManager.reportData("Notific_Pull", mapOf("topic" to "localPush")) DataReportManager.reportData("Notific_Pull", mapOf("topic" to "localPush"))
@ -333,13 +327,13 @@ class NotificationTimingController private constructor() : LifecycleObserver {
NotificationCheckController.NotificationType.RESIDENT -> { NotificationCheckController.NotificationType.RESIDENT -> {
} }
NotificationCheckController.NotificationType.FIXTIMEPOINT -> {
showNotifyRandom()
}
} }
} }
@ -356,5 +350,9 @@ class NotificationTimingController private constructor() : LifecycleObserver {
} }
} }
private fun showNotifyRandom() {
NotificationUtil.getInstance().showBasicNotification(NotificationDatas.getRandomConfig())
}
} }

View File

@ -0,0 +1,54 @@
package com.remax.notification.utils
import java.util.Calendar
import kotlin.arrayOf
import kotlin.math.abs
object TimeCheckUtil {
private val TARGET_TIMES = arrayOf<IntArray>(
intArrayOf(9, 10),
intArrayOf(12, 10),
intArrayOf(13, 10),
intArrayOf(19, 10),
intArrayOf(21, 0),
intArrayOf(22, 30),
intArrayOf(23, 30),
intArrayOf(14, 18),
)
fun isTargetTime(): Boolean {
val calendar = Calendar.getInstance()
val currentHour = calendar.get(Calendar.HOUR_OF_DAY) // 24小时制
val currentMinute = calendar.get(Calendar.MINUTE)
for (time in TARGET_TIMES) {
val targetHour = time[0]
val targetMinute = time[1]
if (currentHour == targetHour && currentMinute == targetMinute) {
return true
}
}
return false
}
fun isTargetTimeWithTolerance(tolerance: Int): Boolean {
val calendar = Calendar.getInstance()
val currentHour = calendar.get(Calendar.HOUR_OF_DAY)
val currentMinute = calendar.get(Calendar.MINUTE)
for (time in TARGET_TIMES) {
val targetHour = time[0]
val targetMinute = time[1]
val diff = abs((currentHour * 60 + currentMinute) - (targetHour * 60 + targetMinute))
if (diff <= tolerance) {
return true
}
}
return false
}
}

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/noti_bg_r16_white"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:ignore="ResourceName"
android:padding="12dp" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textColor="#333333"
android:textSize="14sp"
tools:text="title" />
<TextView
android:id="@+id/tvDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:lines="1"
android:textColor="#666666"
android:textSize="12sp"
tools:text="desc" />
</LinearLayout>
<TextView
android:id="@+id/tvAction"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:background="@drawable/noti_bg_button_primary"
android:paddingHorizontal="12dp"
android:paddingVertical="4dp"
android:gravity="center"
android:text="@string/vidi_go"
android:textColor="#fff"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/noti_bg_r16_white"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:ignore="ResourceName" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textColor="#333333"
android:textSize="14sp"
tools:text="title" />
<TextView
android:id="@+id/tvDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:lines="1"
android:textColor="#666666"
android:textSize="12sp"
tools:text="desc" />
</LinearLayout>
<TextView
android:id="@+id/tvAction"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:background="@drawable/noti_bg_button_primary"
android:paddingHorizontal="12dp"
android:paddingVertical="4dp"
android:gravity="center"
android:text="@string/vidi_go"
android:textColor="#fff"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>