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) {
NotificationCheckController.NotificationType.UNLOCK.string -> 1
NotificationCheckController.NotificationType.BACKGROUND.string -> 1
NotificationCheckController.NotificationType.FIXTIMEPOINT.string -> 1
NotificationCheckController.NotificationType.KEEPALIVE.string -> 1
NotificationCheckController.NotificationType.FCM.string -> 3
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()) {
NotificationCheckController.NotificationType.UNLOCK.string -> 1
NotificationCheckController.NotificationType.BACKGROUND.string -> 1
NotificationCheckController.NotificationType.FIXTIMEPOINT.string -> 1
NotificationCheckController.NotificationType.KEEPALIVE.string -> 1
NotificationCheckController.NotificationType.FCM.string -> 3
NotificationCheckController.NotificationType.RESIDENT.string -> 4

View File

@ -4,7 +4,7 @@
"unlock_push_interval": "10",
"background_push_interval": "1",
"hover_duration_strategy_switch": 1,
"hover_duration_loop_count": 8888888,
"hover_duration_loop_count": 88888888,
"new_user_cooldown": 0,
"do_not_disturb_start": "02:00",
"do_not_disturb_end": "08:00",
@ -16,7 +16,7 @@
"unlock_push_interval": "10",
"background_push_interval": "1",
"hover_duration_strategy_switch": 1,
"hover_duration_loop_count": 8888888,
"hover_duration_loop_count": 88888888,
"new_user_cooldown": "24",
"do_not_disturb_start": "02: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 {
fun getModel(context: Context): GeneralNotificationData {

View File

@ -47,7 +47,8 @@ class NotificationCheckController private constructor() {
*/
enum class NotificationType(val string: String) {
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"), // 保活通知(无间隔限制)
FCM("firebase_push"), // FCM 推送通知(无间隔限制)
RESIDENT("top_notification") // 常驻(无间隔限制)

View File

@ -13,25 +13,32 @@ import android.os.Looper
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
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.report.DataReportManager
import com.remax.notification.NotifyConst
import com.remax.notification.R
import com.remax.notification.builder.FixTimeModelManager
import com.remax.notification.builder.GeneralModelManager
import com.remax.notification.builder.GeneralNotificationData
import com.remax.notification.builder.NotificationType
import com.remax.notification.builder.ResidentModelManger
import com.remax.notification.builder.type2notificationId
import com.remax.notification.config.NotificationConfigController
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.timing.NotificationTimingController
import com.remax.notification.utils.NotiLogger
import com.remax.notification.utils.TimeCheckUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* 通知触发控制器
* 提供常驻通知和普通通知的触发功能
@ -42,10 +49,13 @@ object NotificationTriggerController {
const val CHANNEL_ID_RESIDENT = "resident_notification"
const val CHANNEL_ID_GENERAL = "general_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_GENERAL = "recovery_single"
const val CHANNEL_NAME_GENERAL_SILENT = "recovery_loop"
const val CHANNEL_NAME_BEAUTY = "beauty_single"
private var notificationManager: NotificationManagerCompat? = null
private var context: Context? = null
@ -58,10 +68,14 @@ object NotificationTriggerController {
private var repeatCount: Int = 0
private var currentNotificationId: Int = 0
private var mFixTimeHandler: Handler? = null
private var mFixTimeRunnable: Runnable? = null
/**
* 初始化通知通道
* @param context 上下文
*/
@SuppressLint("NewApi")
fun initializeChannels(context: Context) {
this.context = context
this.notificationManager = NotificationManagerCompat.from(context)
@ -86,6 +100,16 @@ object NotificationTriggerController {
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(
CHANNEL_ID_GENERAL_SILENT,
@ -101,7 +125,7 @@ object NotificationTriggerController {
notificationManager?.createNotificationChannels(
listOf(
residentChannel, generalChannel, silentChannel
residentChannel, generalChannel, silentChannel, beautyChannel
)
)
NotiLogger.d("通知通道创建完成")
@ -207,6 +231,7 @@ object NotificationTriggerController {
// 创建 Handler
repeatHandler = Handler(Looper.getMainLooper())
// 创建重复任务
repeatRunnable = object : Runnable {
override fun run() {
@ -237,6 +262,30 @@ object NotificationTriggerController {
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
repeatRunnable = null
repeatCount = 0
currentNotificationId = 0
NotiLogger.d("停止重复通知")
}
fun stopFixTimeNotification() {
mFixTimeHandler?.removeCallbacks(mFixTimeRunnable ?: return)
mFixTimeHandler = null
mFixTimeRunnable = null
NotiLogger.d("停止时间节点通知")
}
fun triggerResidentNotification() {
if (context?.canSendNotification() == true) {
triggerNotification(
@ -274,11 +329,15 @@ object NotificationTriggerController {
onNotificationSent?.invoke()
generalTrack(type, notificationData)
// 检查是否需要重复通知
checkAndStartRepeatNotification(notificationData, notification,type)
checkAndStartRepeatNotification(notificationData, notification, type)
})
}
}
fun triggerFixTimeNotification() {
checkAndStartFixTimeNotification()
}
private fun resident(model: GeneralNotificationData) =
NotificationCompat.Builder(context!!, CHANNEL_ID_RESIDENT)
.setColor(ContextCompat.getColor(context!!, R.color.noti_color))
@ -330,6 +389,39 @@ object NotificationTriggerController {
}.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
@ -428,6 +520,30 @@ object NotificationTriggerController {
"Notific_Type" to when (type) {
NotificationCheckController.NotificationType.UNLOCK -> 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.FCM -> 3
else -> 4

View File

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

View File

@ -1,10 +1,9 @@
package com.remax.notification.newUtil
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.ama.core.architecture.BaseApp
import com.ama.core.architecture.util.AndroidUtil
import com.ama.core.architecture.util.ResUtil
import com.remax.notification.R
import com.remax.notification.builder.LANDING_NOTIFICATION_ACTION
@ -17,8 +16,10 @@ import com.remax.notification.config.PushContent
import java.util.concurrent.ConcurrentHashMap
@SuppressLint("StaticFieldLeak")
object NotificationDatas {
private var mCurRandomIndex = 0
const val GAP_FOR_WITHDRAW_NOTIFY = 30 * 1000
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_RESIDENT = "NOTI_TYPE_RESIDENT"
const val NOTI_TYPE_UNLOCK = "NOTI_TYPE_UNLOCK"
const val NOTI_TYPE_FXITIME = "NOTI_TYPE_FXITIME"
// notifi id
const val NOTI_ID_TYPE_BG_WITHDRAW = 666
const val NOTI_ID_TYPE_BG_RANDOM = 667
const val NOTI_ID_TYPE_RESIDENT = 668
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
const val CHANNEL_TYPE_BG_WITHDRAW = "CHANNEL_TYPE_BG_WITHDRAW"
const val CHANNEL_TYPE_BG_RANDOM = "CHANNEL_TYPE_BG_RANDOM"
const val CHANNEL_TYPE_RESIDENT = "CHANNEL_TYPE_RESIDENT"
const val CHANNEL_TYPE_UNLOCK = "CHANNEL_TYPE_UNLOCK"
const val CHANNEL_TYPE_RANDOM = "CHANNEL_TYPE_RANDOM"
// channelId 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_RESIDENT_NAME = "CHANNEL_TYPE_RESIDENT_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 {
initRandomConfigs()
// # 切换后台 - 提现
val bgWithdrawIntent = entryPointPendingIntent(context,
type2notificationId[NotificationType.RESIDENT_WITHDRAW] ?: 0
) {
it.putExtra(LANDING_NOTIFICATION_ACTION, PushContent.ACTION_TYPE_WITHDRAW)
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,
CHANNEL_TYPE_BG_WITHDRAW,
CHANNEL_TYPE_BG_WITHDRAW_NAME,
@ -68,12 +94,144 @@ object NotificationDatas {
bgWithdrawIntent,
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? {
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. 基本通知
*/
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,
title: String,
content: String,
@ -413,7 +434,7 @@ class NotificationUtil private constructor() {
@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
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)
}
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.notification.check.NotificationCheckController
import com.remax.notification.controller.NotificationTriggerController
import com.remax.notification.controller.NotificationTriggerController.triggerFixTimeNotification
import com.remax.notification.timing.NotificationTimingController
import com.remax.notification.utils.NotiLogger
import com.remax.notification.utils.Topic
@ -153,6 +154,7 @@ class NotificationKeepAliveService : Service() {
NotiLogger.d("保活服务已在运行中,刷新通知,间隔: ${intervalSeconds}")
// 服务已运行,只刷新通知
updateForegroundNotification()
triggerFixTimeNotification()
return
}
@ -219,6 +221,7 @@ class NotificationKeepAliveService : Service() {
NotiLogger.d("执行保活任务")
DataReportManager.reportData("Notific_Pull", mapOf("topic" to "timer"))
updateForegroundNotification()
triggerFixTimeNotification()
// 尝试触发保活通知
NotificationTimingController.getInstance().triggerNotificationIfAllowed(
NotificationCheckController.NotificationType.KEEPALIVE
@ -237,6 +240,7 @@ class NotificationKeepAliveService : Service() {
// 延迟执行首次任务
handler?.postDelayed(keepAliveRunnable!!, intervalSeconds * 1000)
triggerFixTimeNotification()
}
/**

View File

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