diff --git a/sample-app/src/androidTest/java/com/xuqm/sdk/sample/PushSdkTest.kt b/sample-app/src/androidTest/java/com/xuqm/sdk/sample/PushSdkTest.kt
index 1f57cc9..7978794 100644
--- a/sample-app/src/androidTest/java/com/xuqm/sdk/sample/PushSdkTest.kt
+++ b/sample-app/src/androidTest/java/com/xuqm/sdk/sample/PushSdkTest.kt
@@ -76,8 +76,11 @@ class PushSdkTest {
val expected = when (manufacturer) {
"HUAWEI" -> PushVendor.HUAWEI
"XIAOMI" -> PushVendor.XIAOMI
+ "REDMI", "POCO" -> PushVendor.XIAOMI
"OPPO" -> PushVendor.OPPO
+ "REALME", "ONEPLUS" -> PushVendor.OPPO
"VIVO" -> PushVendor.VIVO
+ "IQOO" -> PushVendor.VIVO
"HONOR" -> PushVendor.HONOR
else -> PushVendor.FCM
}
@@ -102,11 +105,17 @@ class PushSdkTest {
// 1. initializeVendors 不应抛出任何异常
PushSDK.initializeVendors(appCtx)
- // 2. 模拟器上 FCM token 通常为 null
- val reg = PushSDK.currentRegistration(appCtx)
+ // 2. 模拟器上 FCM token 通常为 null;真机只校验厂商映射和流程不崩溃
val vendor = PushSDK.detectVendor()
- // 不做 pushToken 非空断言(emulator 无 Firebase),仅验证 vendor 正确
- assertEquals("模拟器应检测到 FCM", PushVendor.FCM, vendor)
+ val expected = when (Build.MANUFACTURER.uppercase()) {
+ "HUAWEI" -> PushVendor.HUAWEI
+ "XIAOMI", "REDMI", "POCO" -> PushVendor.XIAOMI
+ "OPPO", "REALME", "ONEPLUS" -> PushVendor.OPPO
+ "VIVO", "IQOO" -> PushVendor.VIVO
+ "HONOR" -> PushVendor.HONOR
+ else -> PushVendor.FCM
+ }
+ assertEquals("MANUFACTURER=${Build.MANUFACTURER} 应检测到 $expected", expected, vendor)
// 3. setReceivePush 调用不应崩溃
PushSDK.setReceivePush(appCtx, USER_A, enabled = false)
diff --git a/sdk-push/build.gradle.kts b/sdk-push/build.gradle.kts
index 48a7a55..1fc5548 100644
--- a/sdk-push/build.gradle.kts
+++ b/sdk-push/build.gradle.kts
@@ -11,6 +11,9 @@ android {
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
consumerProguardFiles("consumer-rules.pro")
+ manifestPlaceholders["XUQM_VIVO_APP_ID"] = ""
+ manifestPlaceholders["XUQM_VIVO_APP_KEY"] = ""
+ manifestPlaceholders["XUQM_HONOR_APP_ID"] = ""
}
compileOptions {
@@ -25,14 +28,12 @@ android {
dependencies {
api(project(":sdk-core"))
+ api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.messaging)
-
- // Optional vendor push SDKs — add the ones you need in your app module.
- // These are NOT declared here because they require proprietary Maven repos:
- // Huawei: com.huawei.hms:push (via Huawei Maven repo)
- // Xiaomi: com.xiaomi.mipush:mipush (via Xiaomi Maven repo)
- // OPPO: com.heytap.mcs:push (via OPPO Maven repo)
- // vivo: com.vivo.pushsdk:pushsdk (via vivo Maven repo)
- // Honor: com.hihonor.mcs:push (via Honor Maven repo)
+ api("com.huawei.hms:push:6.12.0.300")
+ api("com.hihonor.mcs:push:7.0.41.301")
+ api("io.github.hebeiliang.mipush:Push:2.0.0")
+ api("com.umeng.umsdk:oppo-push:3.0.0")
+ api("com.umeng.umsdk:vivo-push:4.0.6.0")
}
diff --git a/sdk-push/consumer-rules.pro b/sdk-push/consumer-rules.pro
index 5ff765d..89d0231 100644
--- a/sdk-push/consumer-rules.pro
+++ b/sdk-push/consumer-rules.pro
@@ -9,6 +9,10 @@
# ── Firebase Messaging Service — Android resolves it by class name ────────────
-keep class com.xuqm.sdk.push.fcm.XuqmFirebaseMessagingService { *; }
+-keep class com.xuqm.sdk.push.huawei.XuqmHuaweiPushService { *; }
+-keep class com.xuqm.sdk.push.honor.XuqmHonorPushService { *; }
+-keep class com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver { *; }
+-keep class com.xuqm.sdk.push.xiaomi.XuqmXiaomiPushReceiver { *; }
# ── Vendor push services — instantiated via reflection inside PushSDK ─────────
-keep class com.xuqm.sdk.push.vendor.** { *; }
diff --git a/sdk-push/libs/README.md b/sdk-push/libs/README.md
new file mode 100644
index 0000000..5148f28
--- /dev/null
+++ b/sdk-push/libs/README.md
@@ -0,0 +1,20 @@
+Vendor push SDK binaries can live here when a vendor only provides a console
+download.
+
+The Xuqm push SDK owns the vendor dependency graph. Huawei, Honor, Xiaomi,
+OPPO, and vivo dependencies are declared by `sdk-push/build.gradle.kts`, so app
+integrators do not need to add vendor SDKs themselves.
+
+If a vendor SDK must be pinned to an official console-only binary for a tenant,
+put the vendor `*.aar` or `*.jar` here before publishing the Xuqm push SDK.
+Those binaries are picked up by `api(fileTree(...))`.
+
+Xiaomi's official current AAR download page requires a Xiaomi Push console login
+session. If an app must use that exact official package, download it from the
+tenant's Xiaomi console and publish this SDK with the AAR in this directory.
+
+`sdk-push` owns the Android manifest integration: permissions, push services,
+notification click activity, vendor receivers and metadata placeholders are
+declared in `src/main/AndroidManifest.xml`. Host apps should integrate the Xuqm
+push SDK; vendor credentials are loaded from the tenant platform when the vendor
+SDK supports runtime configuration.
diff --git a/sdk-push/src/main/AndroidManifest.xml b/sdk-push/src/main/AndroidManifest.xml
index b07524d..b17fd6f 100644
--- a/sdk-push/src/main/AndroidManifest.xml
+++ b/sdk-push/src/main/AndroidManifest.xml
@@ -1,6 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -8,5 +52,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt
index aff63b2..28800ab 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/PushSDK.kt
@@ -9,8 +9,10 @@ import com.xuqm.sdk.XuqmSDK
import com.xuqm.sdk.core.ServiceEndpointRegistry
import com.xuqm.sdk.network.ApiClient
import com.xuqm.sdk.push.api.PushApi
+import com.xuqm.sdk.push.api.PushConfigApi
import com.xuqm.sdk.push.model.PushRegistrationSnapshot
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
import com.xuqm.sdk.push.storage.PushRegistrationStore
import com.xuqm.sdk.push.vendor.FcmPushService
import com.xuqm.sdk.push.vendor.HonorPushService
@@ -29,8 +31,11 @@ import java.util.concurrent.atomic.AtomicReference
object PushSDK {
private val api: PushApi get() = ApiClient.create(PushApi::class.java, ServiceEndpointRegistry.pushBaseUrl)
+ private val configApi: PushConfigApi get() = ApiClient.create(PushConfigApi::class.java, ServiceEndpointRegistry.controlBaseUrl)
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val registeredUserId = AtomicReference(null)
+ @Volatile
+ private var cachedVendorConfig: PushVendorConfig? = null
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
XuqmSDK.requireInit()
@@ -59,6 +64,14 @@ object PushSDK {
registerDevice(context, userId)
}
+ fun refreshNativePushToken(
+ context: Context,
+ vendor: PushVendor = detectVendor(),
+ ) {
+ XuqmSDK.requireInit()
+ ensureNativePushToken(context.applicationContext, vendor)
+ }
+
fun unbindImUser(userId: String) {
unregisterDevice(userId)
}
@@ -77,6 +90,7 @@ object PushSDK {
api.setReceivePush(
appId = XuqmSDK.appKey,
userId = resolvedUserId,
+ deviceId = DeviceUtils.getDeviceId(context),
enabled = enabled,
)
if (enabled) {
@@ -107,6 +121,11 @@ object PushSDK {
userId = userId,
vendor = vendor.name,
token = pushToken,
+ deviceId = DeviceUtils.getDeviceId(context),
+ brand = Build.MANUFACTURER.orEmpty(),
+ model = Build.MODEL.orEmpty(),
+ osVersion = DeviceUtils.getOsVersion(),
+ appVersion = appVersion(context),
)
registeredUserId.set(userId)
store(context).updateLastUserId(userId)
@@ -126,7 +145,12 @@ object PushSDK {
deviceId = DeviceUtils.getDeviceId(XuqmSDK.appContext),
fallbackVendor = detectVendor(),
)
- api.unregisterDevice(XuqmSDK.appKey, userId, reg?.vendor?.name ?: detectVendor().name)
+ api.unregisterDevice(
+ XuqmSDK.appKey,
+ userId,
+ reg?.vendor?.name ?: detectVendor().name,
+ DeviceUtils.getDeviceId(XuqmSDK.appContext),
+ )
registeredUserId.compareAndSet(userId, null)
store(XuqmSDK.appContext).updateLastUserId(null)
}
@@ -146,8 +170,11 @@ object PushSDK {
return when (Build.MANUFACTURER.uppercase()) {
"HUAWEI" -> PushVendor.HUAWEI
"XIAOMI" -> PushVendor.XIAOMI
+ "REDMI", "POCO" -> PushVendor.XIAOMI
"OPPO" -> PushVendor.OPPO
+ "REALME", "ONEPLUS" -> PushVendor.OPPO
"VIVO" -> PushVendor.VIVO
+ "IQOO" -> PushVendor.VIVO
"HONOR" -> PushVendor.HONOR
else -> PushVendor.FCM
}
@@ -156,14 +183,17 @@ object PushSDK {
fun initializeVendors(context: Context) {
val detectedVendor = detectVendor()
Log.i("XuqmPushSDK", "Detected push vendor: ${detectedVendor.name}")
- vendorServices.forEach { service ->
- if (service.vendor == detectedVendor && service.isAvailable(context)) {
- Log.i("XuqmPushSDK", "Initializing push vendor: ${service.vendor.name}")
- service.register(context)
+ scope.launch {
+ val config = loadVendorConfig()
+ vendorServices.forEach { service ->
+ if (service.vendor == detectedVendor && service.isAvailable(context)) {
+ Log.i("XuqmPushSDK", "Initializing push vendor: ${service.vendor.name}")
+ service.register(context, config)
+ }
+ }
+ if (detectedVendor == PushVendor.FCM) {
+ ensureNativePushToken(context)
}
- }
- if (detectedVendor == PushVendor.FCM) {
- ensureNativePushToken(context)
}
}
@@ -189,7 +219,13 @@ object PushSDK {
)?.receivePush ?: true
private fun ensureNativePushToken(context: Context) {
- val vendor = detectVendor()
+ ensureNativePushToken(context, detectVendor())
+ }
+
+ private fun ensureNativePushToken(
+ context: Context,
+ vendor: PushVendor,
+ ) {
val registration = currentRegistration(context)
if (registration?.pushToken?.isNotBlank() == true) return
@@ -216,7 +252,9 @@ object PushSDK {
} else {
val service = vendorServices.firstOrNull { it.vendor == vendor }
if (service != null && service.isAvailable(context)) {
- service.register(context)
+ scope.launch {
+ service.register(context, loadVendorConfig())
+ }
} else {
Log.w(
"XuqmPushSDK",
@@ -244,4 +282,37 @@ object PushSDK {
}
}
}
+
+ private fun appVersion(context: Context): String? =
+ runCatching {
+ val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ "${packageInfo.versionName ?: ""}(${packageInfo.longVersionCode})"
+ } else {
+ @Suppress("DEPRECATION")
+ "${packageInfo.versionName ?: ""}(${packageInfo.versionCode})"
+ }
+ }.getOrNull()
+
+ private suspend fun loadVendorConfig(): PushVendorConfig {
+ cachedVendorConfig?.let { return it }
+ val loaded = runCatching {
+ val pushConfig = configApi.sdkConfig(XuqmSDK.appKey).data?.pushConfig
+ PushVendorConfig(
+ huaweiAppId = pushConfig?.getAsJsonObject("huawei")?.get("appId")?.asString.orEmpty(),
+ xiaomiAppId = pushConfig?.getAsJsonObject("xiaomi")?.get("appId")?.asString.orEmpty(),
+ xiaomiAppKey = pushConfig?.getAsJsonObject("xiaomi")?.get("appKey")?.asString.orEmpty(),
+ oppoAppKey = pushConfig?.getAsJsonObject("oppo")?.get("appKey")?.asString.orEmpty(),
+ oppoAppSecret = pushConfig?.getAsJsonObject("oppo")?.get("masterSecret")?.asString.orEmpty(),
+ vivoAppId = pushConfig?.getAsJsonObject("vivo")?.get("appId")?.asString.orEmpty(),
+ vivoAppKey = pushConfig?.getAsJsonObject("vivo")?.get("appKey")?.asString.orEmpty(),
+ honorAppId = pushConfig?.getAsJsonObject("honor")?.get("appId")?.asString.orEmpty(),
+ )
+ }.getOrElse { error ->
+ Log.w("XuqmPushSDK", "Unable to load tenant push config: ${error.message}")
+ PushVendorConfig()
+ }
+ cachedVendorConfig = loaded
+ return loaded
+ }
}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushApi.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushApi.kt
index 621be87..08a65c2 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushApi.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushApi.kt
@@ -12,6 +12,12 @@ interface PushApi {
@Query("userId") userId: String,
@Query("vendor") vendor: String,
@Query("token") token: String,
+ @Query("deviceId") deviceId: String,
+ @Query("brand") brand: String,
+ @Query("model") model: String,
+ @Query("osVersion") osVersion: String,
+ @Query("platform") platform: String = "ANDROID",
+ @Query("appVersion") appVersion: String? = null,
)
@DELETE("api/push/unregister")
@@ -19,12 +25,14 @@ interface PushApi {
@Query("appId") appId: String,
@Query("userId") userId: String,
@Query("vendor") vendor: String,
+ @Query("deviceId") deviceId: String? = null,
)
@POST("api/push/receive-push")
suspend fun setReceivePush(
@Query("appId") appId: String,
@Query("userId") userId: String,
+ @Query("deviceId") deviceId: String? = null,
@Query("enabled") enabled: Boolean,
)
}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushConfigApi.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushConfigApi.kt
new file mode 100644
index 0000000..66e27b5
--- /dev/null
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/api/PushConfigApi.kt
@@ -0,0 +1,21 @@
+package com.xuqm.sdk.push.api
+
+import com.google.gson.JsonObject
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface PushConfigApi {
+ @GET("api/sdk/config")
+ suspend fun sdkConfig(
+ @Query("appId") appId: String,
+ @Query("platform") platform: String = "ANDROID",
+ ): SdkConfigResponse
+}
+
+data class SdkConfigResponse(
+ val data: SdkConfigData? = null,
+)
+
+data class SdkConfigData(
+ val pushConfig: JsonObject? = null,
+)
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/honor/XuqmHonorPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/honor/XuqmHonorPushService.kt
new file mode 100644
index 0000000..e3facc6
--- /dev/null
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/honor/XuqmHonorPushService.kt
@@ -0,0 +1,23 @@
+package com.xuqm.sdk.push.honor
+
+import android.util.Log
+import com.hihonor.push.sdk.HonorMessageService
+import com.xuqm.sdk.push.PushSDK
+import com.xuqm.sdk.push.model.PushVendor
+
+class XuqmHonorPushService : HonorMessageService() {
+
+ override fun onNewToken(token: String?) {
+ super.onNewToken(token)
+ if (token.isNullOrBlank()) return
+ runCatching {
+ PushSDK.updateNativePushToken(applicationContext, PushVendor.HONOR, token)
+ }.onFailure { error ->
+ Log.w(TAG, "Unable to persist Honor push token: ${error.message}")
+ }
+ }
+
+ companion object {
+ private const val TAG = "XuqmHonorPush"
+ }
+}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/huawei/XuqmHuaweiPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/huawei/XuqmHuaweiPushService.kt
new file mode 100644
index 0000000..8ee35c9
--- /dev/null
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/huawei/XuqmHuaweiPushService.kt
@@ -0,0 +1,39 @@
+package com.xuqm.sdk.push.huawei
+
+import android.os.Bundle
+import android.util.Log
+import com.huawei.hms.push.HmsMessageService
+import com.huawei.hms.push.RemoteMessage
+import com.xuqm.sdk.push.PushSDK
+import com.xuqm.sdk.push.model.PushVendor
+
+class XuqmHuaweiPushService : HmsMessageService() {
+
+ override fun onNewToken(token: String?) {
+ super.onNewToken(token)
+ persistToken(token)
+ }
+
+ override fun onNewToken(token: String?, bundle: Bundle?) {
+ super.onNewToken(token, bundle)
+ persistToken(token)
+ }
+
+ override fun onMessageReceived(message: RemoteMessage?) {
+ super.onMessageReceived(message)
+ Log.d(TAG, "Huawei push message received")
+ }
+
+ private fun persistToken(token: String?) {
+ if (token.isNullOrBlank()) return
+ runCatching {
+ PushSDK.updateNativePushToken(applicationContext, PushVendor.HUAWEI, token)
+ }.onFailure { error ->
+ Log.w(TAG, "Unable to persist Huawei push token: ${error.message}")
+ }
+ }
+
+ companion object {
+ private const val TAG = "XuqmHuaweiPush"
+ }
+}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/model/PushVendorConfig.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/model/PushVendorConfig.kt
new file mode 100644
index 0000000..dac46dd
--- /dev/null
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/model/PushVendorConfig.kt
@@ -0,0 +1,12 @@
+package com.xuqm.sdk.push.model
+
+data class PushVendorConfig(
+ val huaweiAppId: String = "",
+ val xiaomiAppId: String = "",
+ val xiaomiAppKey: String = "",
+ val oppoAppKey: String = "",
+ val oppoAppSecret: String = "",
+ val vivoAppId: String = "",
+ val vivoAppKey: String = "",
+ val honorAppId: String = "",
+)
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt
index d7853af..c8444c6 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/FcmPushService.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
/**
* FCM (Firebase Cloud Messaging) push integration.
@@ -23,7 +24,7 @@ class FcmPushService : PushVendorInterface {
}.getOrDefault(false)
}
- override fun register(context: Context) {
+ override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val fcmClass = Class.forName("com.google.firebase.messaging.FirebaseMessaging")
val instance = fcmClass.getMethod("getInstance").invoke(null)
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HonorPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HonorPushService.kt
index 6e0ee84..4a668a0 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HonorPushService.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HonorPushService.kt
@@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
+import java.lang.reflect.Proxy
/**
* 荣耀推送集成框架
@@ -31,13 +33,21 @@ class HonorPushService : PushVendorInterface {
}.getOrDefault(false)
}
- override fun register(context: Context) {
+ override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val honorPushClass = Class.forName("com.hihonor.push.sdk.HonorPushClient")
val instance = honorPushClass.getMethod("getInstance").invoke(null)
- // HonorPushClient.getInstance().init(context, true)
+ val supported = runCatching {
+ honorPushClass.getMethod("checkSupportHonorPush", Context::class.java)
+ .invoke(instance, context) as? Boolean
+ }.getOrDefault(true)
+ if (supported != true) {
+ Log.w(TAG, "Honor push is not supported on this device")
+ return
+ }
honorPushClass.getMethod("init", Context::class.java, Boolean::class.javaPrimitiveType)
- .invoke(instance, context, true)
+ .invoke(instance, context, false)
+ requestToken(context, honorPushClass, instance)
Log.i(TAG, "Honor push registration requested")
}.onFailure { error ->
Log.w(TAG, "Honor push registration failed: ${error.message}")
@@ -58,4 +68,36 @@ class HonorPushService : PushVendorInterface {
companion object {
private const val TAG = "HonorPushService"
}
+
+ private fun requestToken(
+ context: Context,
+ honorPushClass: Class<*>,
+ instance: Any,
+ ) {
+ runCatching {
+ val callbackClass = Class.forName("com.hihonor.push.sdk.HonorPushCallback")
+ val callback = Proxy.newProxyInstance(
+ callbackClass.classLoader,
+ arrayOf(callbackClass),
+ ) { _, method, args ->
+ when (method.name) {
+ "onSuccess" -> {
+ val token = args?.firstOrNull() as? String
+ if (!token.isNullOrBlank()) {
+ PushSDK.updateNativePushToken(context, vendor, token)
+ Log.i(TAG, "Honor push token acquired")
+ }
+ }
+ "onFailure" -> {
+ Log.w(TAG, "Honor push token failed: ${args?.joinToString()}")
+ }
+ }
+ null
+ }
+ honorPushClass.getMethod("getPushToken", callbackClass)
+ .invoke(instance, callback)
+ }.onFailure { error ->
+ Log.w(TAG, "Honor push token request failed: ${error.message}")
+ }
+ }
}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HuaweiPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HuaweiPushService.kt
index f42bee4..e609e24 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HuaweiPushService.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/HuaweiPushService.kt
@@ -4,9 +4,10 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
/**
- * 华为推送集成框架
+ * Android 华为 HMS 推送集成框架。
*
* 需要添加 HMS Push SDK 依赖:
* ```groovy
@@ -36,12 +37,12 @@ class HuaweiPushService : PushVendorInterface {
}.getOrDefault(false)
}
- override fun register(context: Context) {
+ override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val hmsClass = Class.forName("com.huawei.hms.aaid.HmsInstanceId")
val instance = hmsClass.getMethod("getInstance", Context::class.java)
.invoke(null, context)
- val appId = getAppId(context)
+ val appId = config.huaweiAppId.ifBlank { getAppId(context) }
if (appId.isNotBlank()) {
val token = hmsClass.getMethod(
"getToken",
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/OppoPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/OppoPushService.kt
index 7a00274..aa1e1f4 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/OppoPushService.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/OppoPushService.kt
@@ -6,21 +6,11 @@ import android.os.Bundle
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
import java.lang.reflect.Proxy
/**
- * OPPO 推送集成框架
- *
- * 需要添加 OPPO Push SDK 依赖:
- * ```groovy
- * implementation 'com.heytap.mcs:push:3.x.x'
- * ```
- *
- * 在 `AndroidManifest.xml` 的 `` 下配置:
- * ```xml
- *
- *
- * ```
+ * OPPO 推送集成。OPPO Push 依赖由 sdk-push 自身声明,注册参数优先来自租户 PUSH 配置。
*/
class OppoPushService : PushVendorInterface {
@@ -28,49 +18,26 @@ class OppoPushService : PushVendorInterface {
override fun isAvailable(context: Context): Boolean {
return runCatching {
- Class.forName("com.heytap.mcssdk.PushManager")
+ resolvePushManagerClass()
true
}.getOrDefault(false)
}
- override fun register(context: Context) {
+ override fun register(context: Context, config: PushVendorConfig) {
runCatching {
- val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
- val instance = pushManagerClass.getMethod("getInstance").invoke(null)
-
val meta = context.packageManager.getApplicationInfo(
context.packageName, PackageManager.GET_META_DATA
).metaData ?: Bundle.EMPTY
- val appKey = meta.getString("XUQM_OPPO_APP_KEY", "")
- val appSecret = meta.getString("XUQM_OPPO_APP_SECRET", "")
+ val appKey = config.oppoAppKey.ifBlank { meta.getString("XUQM_OPPO_APP_KEY", "") }
+ val appSecret = config.oppoAppSecret.ifBlank { meta.getString("XUQM_OPPO_APP_SECRET", "") }
if (appKey.isNotBlank() && appSecret.isNotBlank()) {
- val callbackClass = Class.forName("com.heytap.mcssdk.callback.PushCallback")
- val proxy = Proxy.newProxyInstance(
- callbackClass.classLoader,
- arrayOf(callbackClass)
- ) { _, method, args ->
- when (method.name) {
- "onRegister" -> {
- val regId = args?.getOrNull(1) as? String
- if (!regId.isNullOrBlank()) {
- PushSDK.updateNativePushToken(context, vendor, regId)
- Log.i(TAG, "OPPO push token acquired")
- }
- }
- }
- null
+ if (!registerWithHeytapMsp(context, appKey, appSecret)) {
+ registerWithLegacyMcs(context, appKey, appSecret)
}
- pushManagerClass.getMethod(
- "register",
- Context::class.java,
- String::class.java,
- String::class.java,
- callbackClass,
- ).invoke(instance, context, appKey, appSecret, proxy)
Log.i(TAG, "OPPO push registration requested")
} else {
- Log.w(TAG, "OPPO appKey/appSecret not configured in meta-data, skipping registration")
+ Log.w(TAG, "OPPO appKey/appSecret not configured, skipping registration")
}
}.onFailure { error ->
Log.w(TAG, "OPPO push registration failed: ${error.message}")
@@ -79,10 +46,13 @@ class OppoPushService : PushVendorInterface {
override fun unregister(context: Context) {
runCatching {
- val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
- val instance = pushManagerClass.getMethod("getInstance").invoke(null)
- pushManagerClass.getMethod("unRegister", Context::class.java)
- .invoke(instance, context)
+ val pushManagerClass = resolvePushManagerClass()
+ if (pushManagerClass.name == "com.heytap.msp.push.HeytapPushManager") {
+ pushManagerClass.getMethod("unRegister").invoke(null)
+ } else {
+ val instance = pushManagerClass.getMethod("getInstance").invoke(null)
+ pushManagerClass.getMethod("unRegister", Context::class.java).invoke(instance, context)
+ }
Log.i(TAG, "OPPO push unregistered")
}.onFailure { error ->
Log.w(TAG, "OPPO push unregistration failed: ${error.message}")
@@ -91,5 +61,90 @@ class OppoPushService : PushVendorInterface {
companion object {
private const val TAG = "OppoPushService"
+
+ private fun resolvePushManagerClass(): Class<*> {
+ return runCatching {
+ Class.forName("com.heytap.msp.push.HeytapPushManager")
+ }.getOrElse {
+ Class.forName("com.heytap.mcssdk.PushManager")
+ }
+ }
+ }
+
+ private fun registerWithHeytapMsp(
+ context: Context,
+ appKey: String,
+ appSecret: String,
+ ): Boolean {
+ val pushManagerClass = runCatching {
+ Class.forName("com.heytap.msp.push.HeytapPushManager")
+ }.getOrNull() ?: return false
+
+ runCatching {
+ pushManagerClass.getMethod("init", Context::class.java, java.lang.Boolean.TYPE)
+ .invoke(null, context, false)
+ }
+
+ val supported = runCatching {
+ pushManagerClass.getMethod("isSupportPush", Context::class.java)
+ .invoke(null, context) as? Boolean
+ }.getOrElse {
+ runCatching { pushManagerClass.getMethod("isSupportPush").invoke(null) as? Boolean }
+ .getOrDefault(true)
+ } ?: true
+
+ if (!supported) {
+ Log.w(TAG, "OPPO push is not supported on this device")
+ return true
+ }
+
+ val callbackClass = Class.forName("com.heytap.msp.push.callback.ICallBackResultService")
+ val callback = buildCallback(context.applicationContext, callbackClass)
+ pushManagerClass.getMethod(
+ "register",
+ Context::class.java,
+ String::class.java,
+ String::class.java,
+ callbackClass,
+ ).invoke(null, context, appKey, appSecret, callback)
+ runCatching { pushManagerClass.getMethod("requestNotificationPermission").invoke(null) }
+ return true
+ }
+
+ private fun registerWithLegacyMcs(
+ context: Context,
+ appKey: String,
+ appSecret: String,
+ ) {
+ val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
+ val instance = pushManagerClass.getMethod("getInstance").invoke(null)
+ val callbackClass = Class.forName("com.heytap.mcssdk.callback.PushCallback")
+ val callback = buildCallback(context.applicationContext, callbackClass)
+ pushManagerClass.getMethod(
+ "register",
+ Context::class.java,
+ String::class.java,
+ String::class.java,
+ callbackClass,
+ ).invoke(instance, context, appKey, appSecret, callback)
+ }
+
+ private fun buildCallback(context: Context, callbackClass: Class<*>): Any {
+ return Proxy.newProxyInstance(
+ callbackClass.classLoader,
+ arrayOf(callbackClass)
+ ) { _, method, args ->
+ if (method.name == "onRegister") {
+ val code = args?.getOrNull(0) as? Int
+ val regId = args?.getOrNull(1) as? String
+ if ((code == null || code == 0) && !regId.isNullOrBlank()) {
+ PushSDK.updateNativePushToken(context, vendor, regId)
+ Log.i(TAG, "OPPO push token acquired")
+ } else {
+ Log.w(TAG, "OPPO push register callback code=$code")
+ }
+ }
+ null
+ }
}
}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/PushVendorInterface.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/PushVendorInterface.kt
index 6b32f8c..3bb56a9 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/PushVendorInterface.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/PushVendorInterface.kt
@@ -2,6 +2,7 @@ package com.xuqm.sdk.push.vendor
import android.content.Context
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 厂商推送服务接口
@@ -22,7 +23,7 @@ interface PushVendorInterface {
/**
* 注册厂商推送服务,触发获取推送 Token 的流程。
*/
- fun register(context: Context)
+ fun register(context: Context, config: PushVendorConfig = PushVendorConfig())
/**
* 注销厂商推送服务。
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/VivoPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/VivoPushService.kt
index 11348e6..e15d7b7 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/VivoPushService.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/VivoPushService.kt
@@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
+import java.lang.reflect.Proxy
/**
* vivo 推送集成框架
@@ -31,13 +33,33 @@ class VivoPushService : PushVendorInterface {
}.getOrDefault(false)
}
- override fun register(context: Context) {
+ override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val pushClientClass = Class.forName("com.vivo.push.PushClient")
val instance = pushClientClass.getMethod("getInstance", Context::class.java)
.invoke(null, context)
- // PushClient.getInstance(context).initialize()
pushClientClass.getMethod("initialize").invoke(instance)
+ val listenerClass = Class.forName("com.vivo.push.IPushActionListener")
+ val listener = Proxy.newProxyInstance(
+ listenerClass.classLoader,
+ arrayOf(listenerClass),
+ ) { _, method, args ->
+ if (method.name == "onStateChanged") {
+ val state = args?.firstOrNull() as? Int
+ if (state == 0) {
+ persistRegId(context, pushClientClass, instance)
+ } else {
+ Log.w(TAG, "Vivo push turnOnPush state=$state")
+ }
+ }
+ null
+ }
+ runCatching {
+ pushClientClass.getMethod("turnOnPush", listenerClass)
+ .invoke(instance, listener)
+ }.onFailure {
+ persistRegId(context, pushClientClass, instance)
+ }
Log.i(TAG, "Vivo push registration requested")
}.onFailure { error ->
Log.w(TAG, "Vivo push registration failed: ${error.message}")
@@ -59,4 +81,18 @@ class VivoPushService : PushVendorInterface {
companion object {
private const val TAG = "VivoPushService"
}
+
+ private fun persistRegId(
+ context: Context,
+ pushClientClass: Class<*>,
+ instance: Any,
+ ) {
+ val regId = runCatching {
+ pushClientClass.getMethod("getRegId").invoke(instance) as? String
+ }.getOrNull()
+ if (!regId.isNullOrBlank()) {
+ PushSDK.updateNativePushToken(context, vendor, regId)
+ Log.i(TAG, "Vivo push token acquired")
+ }
+ }
}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/XiaomiPushService.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/XiaomiPushService.kt
index 389199a..fc3ad93 100644
--- a/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/XiaomiPushService.kt
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vendor/XiaomiPushService.kt
@@ -6,26 +6,13 @@ import android.os.Bundle
import android.util.Log
import com.xuqm.sdk.push.PushSDK
import com.xuqm.sdk.push.model.PushVendor
+import com.xuqm.sdk.push.model.PushVendorConfig
/**
* 小米推送集成框架
*
- * 需要添加 MiPush SDK 依赖:
- * ```groovy
- * implementation 'com.xiaomi.mipush:mipush:5.x.x'
- * ```
- *
- * 在 `AndroidManifest.xml` 的 `` 下配置:
- * ```xml
- *
- *
- * ```
- *
- * 并在自定义 `PushMessageReceiver` 的 `onReceiveRegisterResult()` 回调中获取 token,
- * 然后调用:
- * ```kotlin
- * PushSDK.updateNativePushToken(context, PushVendor.XIAOMI, token)
- * ```
+ * MiPush AAR 需要随 sdk-push 发布。注册参数优先来自租户 PUSH 配置,
+ * 旧版本 manifest meta-data 仅作为兼容兜底。
*/
class XiaomiPushService : PushVendorInterface {
@@ -38,14 +25,14 @@ class XiaomiPushService : PushVendorInterface {
}.getOrDefault(false)
}
- override fun register(context: Context) {
+ override fun register(context: Context, config: PushVendorConfig) {
runCatching {
val miPushClass = Class.forName("com.xiaomi.mipush.sdk.MiPushClient")
val meta = context.packageManager.getApplicationInfo(
context.packageName, PackageManager.GET_META_DATA
).metaData ?: Bundle.EMPTY
- val appId = meta.getString("XUQM_XIAOMI_APP_ID", "")
- val appKey = meta.getString("XUQM_XIAOMI_APP_KEY", "")
+ val appId = config.xiaomiAppId.ifBlank { meta.getString("XUQM_XIAOMI_APP_ID", "") }
+ val appKey = config.xiaomiAppKey.ifBlank { meta.getString("XUQM_XIAOMI_APP_KEY", "") }
if (appId.isNotBlank() && appKey.isNotBlank()) {
miPushClass.getMethod(
"registerPush",
@@ -54,8 +41,9 @@ class XiaomiPushService : PushVendorInterface {
String::class.java,
).invoke(null, context, appId, appKey)
Log.i(TAG, "Xiaomi push registration requested")
+ pollRegId(context, miPushClass)
} else {
- Log.w(TAG, "Xiaomi appId/appKey not configured in meta-data, skipping registration")
+ Log.w(TAG, "Xiaomi appId/appKey not configured, skipping registration")
}
}.onFailure { error ->
Log.w(TAG, "Xiaomi push registration failed: ${error.message}")
@@ -73,6 +61,24 @@ class XiaomiPushService : PushVendorInterface {
}
}
+ private fun pollRegId(context: Context, miPushClass: Class<*>) {
+ Thread {
+ repeat(8) {
+ val regId = runCatching {
+ miPushClass.getMethod("getRegId", Context::class.java)
+ .invoke(null, context) as? String
+ }.getOrNull()
+ if (!regId.isNullOrBlank()) {
+ PushSDK.updateNativePushToken(context, vendor, regId)
+ Log.i(TAG, "Xiaomi push token acquired")
+ return@Thread
+ }
+ Thread.sleep(1000)
+ }
+ Log.w(TAG, "Xiaomi push token not ready after registration")
+ }.start()
+ }
+
companion object {
private const val TAG = "XiaomiPushService"
}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/vivo/XuqmVivoPushReceiver.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/vivo/XuqmVivoPushReceiver.kt
new file mode 100644
index 0000000..c214d71
--- /dev/null
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/vivo/XuqmVivoPushReceiver.kt
@@ -0,0 +1,47 @@
+package com.xuqm.sdk.push.vivo
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.xuqm.sdk.push.PushSDK
+import com.xuqm.sdk.push.model.PushVendor
+
+class XuqmVivoPushReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent?) {
+ if (intent == null) return
+ val appContext = context.applicationContext
+ val regId = extractRegId(intent) ?: currentRegId(appContext)
+ if (regId.isNullOrBlank()) return
+ runCatching {
+ PushSDK.updateNativePushToken(appContext, PushVendor.VIVO, regId)
+ }.onFailure { error ->
+ Log.w(TAG, "Unable to persist vivo push token: ${error.message}")
+ }
+ }
+
+ private fun extractRegId(intent: Intent): String? {
+ val extras = intent.extras ?: return null
+ for (key in extras.keySet()) {
+ val value = extras.get(key) as? String ?: continue
+ if (key.contains("reg", ignoreCase = true) || key.contains("token", ignoreCase = true)) {
+ return value.takeIf { it.isNotBlank() }
+ }
+ }
+ return null
+ }
+
+ private fun currentRegId(context: Context): String? {
+ return runCatching {
+ val pushClientClass = Class.forName("com.vivo.push.PushClient")
+ val instance = pushClientClass.getMethod("getInstance", Context::class.java)
+ .invoke(null, context)
+ pushClientClass.getMethod("getRegId").invoke(instance) as? String
+ }.getOrNull()
+ }
+
+ companion object {
+ private const val TAG = "XuqmVivoPush"
+ }
+}
diff --git a/sdk-push/src/main/java/com/xuqm/sdk/push/xiaomi/XuqmXiaomiPushReceiver.kt b/sdk-push/src/main/java/com/xuqm/sdk/push/xiaomi/XuqmXiaomiPushReceiver.kt
new file mode 100644
index 0000000..8c3c05f
--- /dev/null
+++ b/sdk-push/src/main/java/com/xuqm/sdk/push/xiaomi/XuqmXiaomiPushReceiver.kt
@@ -0,0 +1,60 @@
+package com.xuqm.sdk.push.xiaomi
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.xuqm.sdk.push.PushSDK
+import com.xuqm.sdk.push.model.PushVendor
+
+class XuqmXiaomiPushReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent?) {
+ if (intent == null) return
+ val appContext = context.applicationContext
+ val regId = extractRegId(intent)
+ if (!regId.isNullOrBlank()) {
+ runCatching {
+ PushSDK.updateNativePushToken(appContext, PushVendor.XIAOMI, regId)
+ }.onFailure { error ->
+ Log.w(TAG, "Unable to persist Xiaomi push token: ${error.message}")
+ }
+ return
+ }
+ runCatching {
+ PushSDK.refreshNativePushToken(appContext, PushVendor.XIAOMI)
+ }.onFailure { error ->
+ Log.d(TAG, "Xiaomi push callback ignored before SDK init: ${error.message}")
+ }
+ }
+
+ private fun extractRegId(intent: Intent): String? {
+ val extras = intent.extras ?: return null
+ for (key in extras.keySet()) {
+ val value = extras.get(key) ?: continue
+ if (value is String && key.contains("reg", ignoreCase = true)) {
+ return value.takeIf { it.isNotBlank() }
+ }
+ if (value.javaClass.name == "com.xiaomi.mipush.sdk.MiPushCommandMessage") {
+ val resultCode = runCatching {
+ value.javaClass.getMethod("getResultCode").invoke(value) as? Long
+ }.getOrNull()
+ val command = runCatching {
+ value.javaClass.getMethod("getCommand").invoke(value) as? String
+ }.getOrNull()
+ val arguments = runCatching {
+ @Suppress("UNCHECKED_CAST")
+ value.javaClass.getMethod("getCommandArguments").invoke(value) as? List
+ }.getOrNull()
+ if ((resultCode == null || resultCode == 0L) && command.equals("register", ignoreCase = true)) {
+ return arguments?.firstOrNull { it.isNotBlank() }
+ }
+ }
+ }
+ return null
+ }
+
+ companion object {
+ private const val TAG = "XuqmMiPushReceiver"
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b28fa77..c469d64 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,6 +1,7 @@
pluginManagement {
repositories {
maven(url = "https://nexus.xuqinmin.com/repository/android/")
+ maven(url = "https://developer.hihonor.com/repo")
google()
mavenCentral()
gradlePluginPortal()
@@ -11,6 +12,7 @@ dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven(url = "https://nexus.xuqinmin.com/repository/android/")
+ maven(url = "https://developer.hihonor.com/repo")
google()
mavenCentral()
}