feat(push): 添加多厂商推送集成支持
- 实现了华为 HMS 推送服务集成 - 实现了小米推送服务集成 - 实现了 OPPO 推送服务集成 - 实现了 vivo 推送服务集成 - 实现了荣耀推送服务集成 - 实现了 FCM 推送服务集成 - 添加了统一的厂商推送接口和检测机制 - 添加了推送配置 API 和存储管理 - 添加了推送令牌管理和设备注册功能 - 添加了模拟器环境的推送测试用例
这个提交包含在:
父节点
19e7b27d6e
当前提交
ea693d5c66
@ -76,8 +76,11 @@ class PushSdkTest {
|
|||||||
val expected = when (manufacturer) {
|
val expected = when (manufacturer) {
|
||||||
"HUAWEI" -> PushVendor.HUAWEI
|
"HUAWEI" -> PushVendor.HUAWEI
|
||||||
"XIAOMI" -> PushVendor.XIAOMI
|
"XIAOMI" -> PushVendor.XIAOMI
|
||||||
|
"REDMI", "POCO" -> PushVendor.XIAOMI
|
||||||
"OPPO" -> PushVendor.OPPO
|
"OPPO" -> PushVendor.OPPO
|
||||||
|
"REALME", "ONEPLUS" -> PushVendor.OPPO
|
||||||
"VIVO" -> PushVendor.VIVO
|
"VIVO" -> PushVendor.VIVO
|
||||||
|
"IQOO" -> PushVendor.VIVO
|
||||||
"HONOR" -> PushVendor.HONOR
|
"HONOR" -> PushVendor.HONOR
|
||||||
else -> PushVendor.FCM
|
else -> PushVendor.FCM
|
||||||
}
|
}
|
||||||
@ -102,11 +105,17 @@ class PushSdkTest {
|
|||||||
// 1. initializeVendors 不应抛出任何异常
|
// 1. initializeVendors 不应抛出任何异常
|
||||||
PushSDK.initializeVendors(appCtx)
|
PushSDK.initializeVendors(appCtx)
|
||||||
|
|
||||||
// 2. 模拟器上 FCM token 通常为 null
|
// 2. 模拟器上 FCM token 通常为 null;真机只校验厂商映射和流程不崩溃
|
||||||
val reg = PushSDK.currentRegistration(appCtx)
|
|
||||||
val vendor = PushSDK.detectVendor()
|
val vendor = PushSDK.detectVendor()
|
||||||
// 不做 pushToken 非空断言(emulator 无 Firebase),仅验证 vendor 正确
|
val expected = when (Build.MANUFACTURER.uppercase()) {
|
||||||
assertEquals("模拟器应检测到 FCM", PushVendor.FCM, vendor)
|
"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 调用不应崩溃
|
// 3. setReceivePush 调用不应崩溃
|
||||||
PushSDK.setReceivePush(appCtx, USER_A, enabled = false)
|
PushSDK.setReceivePush(appCtx, USER_A, enabled = false)
|
||||||
|
|||||||
@ -11,6 +11,9 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = libs.versions.minSdk.get().toInt()
|
minSdk = libs.versions.minSdk.get().toInt()
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
manifestPlaceholders["XUQM_VIVO_APP_ID"] = ""
|
||||||
|
manifestPlaceholders["XUQM_VIVO_APP_KEY"] = ""
|
||||||
|
manifestPlaceholders["XUQM_HONOR_APP_ID"] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -25,14 +28,12 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":sdk-core"))
|
api(project(":sdk-core"))
|
||||||
|
api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
|
||||||
implementation(platform(libs.firebase.bom))
|
implementation(platform(libs.firebase.bom))
|
||||||
implementation(libs.firebase.messaging)
|
implementation(libs.firebase.messaging)
|
||||||
|
api("com.huawei.hms:push:6.12.0.300")
|
||||||
// Optional vendor push SDKs — add the ones you need in your app module.
|
api("com.hihonor.mcs:push:7.0.41.301")
|
||||||
// These are NOT declared here because they require proprietary Maven repos:
|
api("io.github.hebeiliang.mipush:Push:2.0.0")
|
||||||
// Huawei: com.huawei.hms:push (via Huawei Maven repo)
|
api("com.umeng.umsdk:oppo-push:3.0.0")
|
||||||
// Xiaomi: com.xiaomi.mipush:mipush (via Xiaomi Maven repo)
|
api("com.umeng.umsdk:vivo-push:4.0.6.0")
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,10 @@
|
|||||||
|
|
||||||
# ── Firebase Messaging Service — Android resolves it by class name ────────────
|
# ── Firebase Messaging Service — Android resolves it by class name ────────────
|
||||||
-keep class com.xuqm.sdk.push.fcm.XuqmFirebaseMessagingService { *; }
|
-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 ─────────
|
# ── Vendor push services — instantiated via reflection inside PushSDK ─────────
|
||||||
-keep class com.xuqm.sdk.push.vendor.** { *; }
|
-keep class com.xuqm.sdk.push.vendor.** { *; }
|
||||||
|
|||||||
20
sdk-push/libs/README.md
普通文件
20
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.
|
||||||
@ -1,6 +1,50 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE" />
|
||||||
|
<uses-permission android:name="com.heytap.mcs.permission.RECIEVE_MCS_MESSAGE" />
|
||||||
|
<uses-permission android:name="com.hihonor.push.permission.READ_PUSH_NOTIFICATION_INFO" />
|
||||||
|
<uses-permission android:name="${applicationId}.permission.MIPUSH_RECEIVE" />
|
||||||
|
|
||||||
|
<permission
|
||||||
|
android:name="${applicationId}.permission.MIPUSH_RECEIVE"
|
||||||
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
|
<permission
|
||||||
|
android:name="${applicationId}.hihonor.permission.PROCESS_PUSH_MSG"
|
||||||
|
android:protectionLevel="signatureOrSystem" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.huawei.hwid" />
|
||||||
|
<package android:name="com.huawei.hms" />
|
||||||
|
<package android:name="com.huawei.android.pushagent" />
|
||||||
|
<package android:name="com.hihonor.id" />
|
||||||
|
<package android:name="com.hihonor.appmarket" />
|
||||||
|
<intent>
|
||||||
|
<action android:name="com.hihonor.push.action.BIND_PUSH_SERVICE" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
|
<meta-data
|
||||||
|
android:name="push_kit_auto_init_enabled"
|
||||||
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.vivo.push.app_id"
|
||||||
|
android:value="${XUQM_VIVO_APP_ID}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.vivo.push.api_key"
|
||||||
|
android:value="${XUQM_VIVO_APP_KEY}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.hihonor.push.app_id"
|
||||||
|
android:value="${XUQM_HONOR_APP_ID}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.hihonor.push.sdk_version"
|
||||||
|
android:value="7.0.41.301" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.xuqm.sdk.push.fcm.XuqmFirebaseMessagingService"
|
android:name="com.xuqm.sdk.push.fcm.XuqmFirebaseMessagingService"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
@ -8,5 +52,102 @@
|
|||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name="com.xuqm.sdk.push.huawei.XuqmHuaweiPushService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
android:name="com.xuqm.sdk.push.honor.XuqmHonorPushService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.hihonor.push.action.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.xiaomi.push.service.XMPushService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:process=":pushservice" />
|
||||||
|
<service
|
||||||
|
android:name="com.xiaomi.push.service.XMJobService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
android:process=":pushservice" />
|
||||||
|
<service
|
||||||
|
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="com.xiaomi.xmsf.permission.MIPUSH_RECEIVE" />
|
||||||
|
<service
|
||||||
|
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
|
||||||
|
android:enabled="true" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="com.xiaomi.push.service.receivers.PingReceiver"
|
||||||
|
android:exported="false"
|
||||||
|
android:process=":pushservice">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.xiaomi.push.PING_TIMER" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.xiaomi.mipush.sdk.NotificationClickedActivity"
|
||||||
|
android:enabled="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
||||||
|
<meta-data
|
||||||
|
android:name="supportStyle"
|
||||||
|
android:value="scene|voip" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="com.xuqm.sdk.push.xiaomi.XuqmXiaomiPushReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.xiaomi.mipush.ERROR" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.heytap.msp.push.service.CompatibleDataMessageCallbackService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="com.coloros.mcs.permission.SEND_MCS_MESSAGE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.coloros.mcs.action.RECEIVE_MCS_MESSAGE" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
android:name="com.heytap.msp.push.service.DataMessageCallbackService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="com.heytap.mcs.permission.SEND_PUSH_MESSAGE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.heytap.mcs.action.RECEIVE_MCS_MESSAGE" />
|
||||||
|
<action android:name="com.heytap.msp.push.RECEIVE_MCS_MESSAGE" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.vivo.push.sdk.service.CommandClientService"
|
||||||
|
android:exported="true" />
|
||||||
|
<receiver
|
||||||
|
android:name="com.xuqm.sdk.push.vivo.XuqmVivoPushReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.vivo.pushclient.action.RECEIVE" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@ -9,8 +9,10 @@ import com.xuqm.sdk.XuqmSDK
|
|||||||
import com.xuqm.sdk.core.ServiceEndpointRegistry
|
import com.xuqm.sdk.core.ServiceEndpointRegistry
|
||||||
import com.xuqm.sdk.network.ApiClient
|
import com.xuqm.sdk.network.ApiClient
|
||||||
import com.xuqm.sdk.push.api.PushApi
|
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.PushRegistrationSnapshot
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
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.storage.PushRegistrationStore
|
||||||
import com.xuqm.sdk.push.vendor.FcmPushService
|
import com.xuqm.sdk.push.vendor.FcmPushService
|
||||||
import com.xuqm.sdk.push.vendor.HonorPushService
|
import com.xuqm.sdk.push.vendor.HonorPushService
|
||||||
@ -29,8 +31,11 @@ import java.util.concurrent.atomic.AtomicReference
|
|||||||
object PushSDK {
|
object PushSDK {
|
||||||
|
|
||||||
private val api: PushApi get() = ApiClient.create(PushApi::class.java, ServiceEndpointRegistry.pushBaseUrl)
|
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 scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
private val registeredUserId = AtomicReference<String?>(null)
|
private val registeredUserId = AtomicReference<String?>(null)
|
||||||
|
@Volatile
|
||||||
|
private var cachedVendorConfig: PushVendorConfig? = null
|
||||||
|
|
||||||
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
|
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
|
||||||
XuqmSDK.requireInit()
|
XuqmSDK.requireInit()
|
||||||
@ -59,6 +64,14 @@ object PushSDK {
|
|||||||
registerDevice(context, userId)
|
registerDevice(context, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refreshNativePushToken(
|
||||||
|
context: Context,
|
||||||
|
vendor: PushVendor = detectVendor(),
|
||||||
|
) {
|
||||||
|
XuqmSDK.requireInit()
|
||||||
|
ensureNativePushToken(context.applicationContext, vendor)
|
||||||
|
}
|
||||||
|
|
||||||
fun unbindImUser(userId: String) {
|
fun unbindImUser(userId: String) {
|
||||||
unregisterDevice(userId)
|
unregisterDevice(userId)
|
||||||
}
|
}
|
||||||
@ -77,6 +90,7 @@ object PushSDK {
|
|||||||
api.setReceivePush(
|
api.setReceivePush(
|
||||||
appId = XuqmSDK.appKey,
|
appId = XuqmSDK.appKey,
|
||||||
userId = resolvedUserId,
|
userId = resolvedUserId,
|
||||||
|
deviceId = DeviceUtils.getDeviceId(context),
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
)
|
)
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@ -107,6 +121,11 @@ object PushSDK {
|
|||||||
userId = userId,
|
userId = userId,
|
||||||
vendor = vendor.name,
|
vendor = vendor.name,
|
||||||
token = pushToken,
|
token = pushToken,
|
||||||
|
deviceId = DeviceUtils.getDeviceId(context),
|
||||||
|
brand = Build.MANUFACTURER.orEmpty(),
|
||||||
|
model = Build.MODEL.orEmpty(),
|
||||||
|
osVersion = DeviceUtils.getOsVersion(),
|
||||||
|
appVersion = appVersion(context),
|
||||||
)
|
)
|
||||||
registeredUserId.set(userId)
|
registeredUserId.set(userId)
|
||||||
store(context).updateLastUserId(userId)
|
store(context).updateLastUserId(userId)
|
||||||
@ -126,7 +145,12 @@ object PushSDK {
|
|||||||
deviceId = DeviceUtils.getDeviceId(XuqmSDK.appContext),
|
deviceId = DeviceUtils.getDeviceId(XuqmSDK.appContext),
|
||||||
fallbackVendor = detectVendor(),
|
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)
|
registeredUserId.compareAndSet(userId, null)
|
||||||
store(XuqmSDK.appContext).updateLastUserId(null)
|
store(XuqmSDK.appContext).updateLastUserId(null)
|
||||||
}
|
}
|
||||||
@ -146,8 +170,11 @@ object PushSDK {
|
|||||||
return when (Build.MANUFACTURER.uppercase()) {
|
return when (Build.MANUFACTURER.uppercase()) {
|
||||||
"HUAWEI" -> PushVendor.HUAWEI
|
"HUAWEI" -> PushVendor.HUAWEI
|
||||||
"XIAOMI" -> PushVendor.XIAOMI
|
"XIAOMI" -> PushVendor.XIAOMI
|
||||||
|
"REDMI", "POCO" -> PushVendor.XIAOMI
|
||||||
"OPPO" -> PushVendor.OPPO
|
"OPPO" -> PushVendor.OPPO
|
||||||
|
"REALME", "ONEPLUS" -> PushVendor.OPPO
|
||||||
"VIVO" -> PushVendor.VIVO
|
"VIVO" -> PushVendor.VIVO
|
||||||
|
"IQOO" -> PushVendor.VIVO
|
||||||
"HONOR" -> PushVendor.HONOR
|
"HONOR" -> PushVendor.HONOR
|
||||||
else -> PushVendor.FCM
|
else -> PushVendor.FCM
|
||||||
}
|
}
|
||||||
@ -156,14 +183,17 @@ object PushSDK {
|
|||||||
fun initializeVendors(context: Context) {
|
fun initializeVendors(context: Context) {
|
||||||
val detectedVendor = detectVendor()
|
val detectedVendor = detectVendor()
|
||||||
Log.i("XuqmPushSDK", "Detected push vendor: ${detectedVendor.name}")
|
Log.i("XuqmPushSDK", "Detected push vendor: ${detectedVendor.name}")
|
||||||
vendorServices.forEach { service ->
|
scope.launch {
|
||||||
if (service.vendor == detectedVendor && service.isAvailable(context)) {
|
val config = loadVendorConfig()
|
||||||
Log.i("XuqmPushSDK", "Initializing push vendor: ${service.vendor.name}")
|
vendorServices.forEach { service ->
|
||||||
service.register(context)
|
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
|
)?.receivePush ?: true
|
||||||
|
|
||||||
private fun ensureNativePushToken(context: Context) {
|
private fun ensureNativePushToken(context: Context) {
|
||||||
val vendor = detectVendor()
|
ensureNativePushToken(context, detectVendor())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureNativePushToken(
|
||||||
|
context: Context,
|
||||||
|
vendor: PushVendor,
|
||||||
|
) {
|
||||||
val registration = currentRegistration(context)
|
val registration = currentRegistration(context)
|
||||||
if (registration?.pushToken?.isNotBlank() == true) return
|
if (registration?.pushToken?.isNotBlank() == true) return
|
||||||
|
|
||||||
@ -216,7 +252,9 @@ object PushSDK {
|
|||||||
} else {
|
} else {
|
||||||
val service = vendorServices.firstOrNull { it.vendor == vendor }
|
val service = vendorServices.firstOrNull { it.vendor == vendor }
|
||||||
if (service != null && service.isAvailable(context)) {
|
if (service != null && service.isAvailable(context)) {
|
||||||
service.register(context)
|
scope.launch {
|
||||||
|
service.register(context, loadVendorConfig())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(
|
Log.w(
|
||||||
"XuqmPushSDK",
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,12 @@ interface PushApi {
|
|||||||
@Query("userId") userId: String,
|
@Query("userId") userId: String,
|
||||||
@Query("vendor") vendor: String,
|
@Query("vendor") vendor: String,
|
||||||
@Query("token") token: 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")
|
@DELETE("api/push/unregister")
|
||||||
@ -19,12 +25,14 @@ interface PushApi {
|
|||||||
@Query("appId") appId: String,
|
@Query("appId") appId: String,
|
||||||
@Query("userId") userId: String,
|
@Query("userId") userId: String,
|
||||||
@Query("vendor") vendor: String,
|
@Query("vendor") vendor: String,
|
||||||
|
@Query("deviceId") deviceId: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@POST("api/push/receive-push")
|
@POST("api/push/receive-push")
|
||||||
suspend fun setReceivePush(
|
suspend fun setReceivePush(
|
||||||
@Query("appId") appId: String,
|
@Query("appId") appId: String,
|
||||||
@Query("userId") userId: String,
|
@Query("userId") userId: String,
|
||||||
|
@Query("deviceId") deviceId: String? = null,
|
||||||
@Query("enabled") enabled: Boolean,
|
@Query("enabled") enabled: Boolean,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
)
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 = "",
|
||||||
|
)
|
||||||
@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.xuqm.sdk.push.PushSDK
|
import com.xuqm.sdk.push.PushSDK
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
import com.xuqm.sdk.push.model.PushVendor
|
||||||
|
import com.xuqm.sdk.push.model.PushVendorConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FCM (Firebase Cloud Messaging) push integration.
|
* FCM (Firebase Cloud Messaging) push integration.
|
||||||
@ -23,7 +24,7 @@ class FcmPushService : PushVendorInterface {
|
|||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun register(context: Context) {
|
override fun register(context: Context, config: PushVendorConfig) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val fcmClass = Class.forName("com.google.firebase.messaging.FirebaseMessaging")
|
val fcmClass = Class.forName("com.google.firebase.messaging.FirebaseMessaging")
|
||||||
val instance = fcmClass.getMethod("getInstance").invoke(null)
|
val instance = fcmClass.getMethod("getInstance").invoke(null)
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import android.content.Context
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.xuqm.sdk.push.PushSDK
|
import com.xuqm.sdk.push.PushSDK
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
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)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun register(context: Context) {
|
override fun register(context: Context, config: PushVendorConfig) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val honorPushClass = Class.forName("com.hihonor.push.sdk.HonorPushClient")
|
val honorPushClass = Class.forName("com.hihonor.push.sdk.HonorPushClient")
|
||||||
val instance = honorPushClass.getMethod("getInstance").invoke(null)
|
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)
|
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")
|
Log.i(TAG, "Honor push registration requested")
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
Log.w(TAG, "Honor push registration failed: ${error.message}")
|
Log.w(TAG, "Honor push registration failed: ${error.message}")
|
||||||
@ -58,4 +68,36 @@ class HonorPushService : PushVendorInterface {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "HonorPushService"
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,10 @@ import android.content.Context
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.xuqm.sdk.push.PushSDK
|
import com.xuqm.sdk.push.PushSDK
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
import com.xuqm.sdk.push.model.PushVendor
|
||||||
|
import com.xuqm.sdk.push.model.PushVendorConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 华为推送集成框架
|
* Android 华为 HMS 推送集成框架。
|
||||||
*
|
*
|
||||||
* 需要添加 HMS Push SDK 依赖:
|
* 需要添加 HMS Push SDK 依赖:
|
||||||
* ```groovy
|
* ```groovy
|
||||||
@ -36,12 +37,12 @@ class HuaweiPushService : PushVendorInterface {
|
|||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun register(context: Context) {
|
override fun register(context: Context, config: PushVendorConfig) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val hmsClass = Class.forName("com.huawei.hms.aaid.HmsInstanceId")
|
val hmsClass = Class.forName("com.huawei.hms.aaid.HmsInstanceId")
|
||||||
val instance = hmsClass.getMethod("getInstance", Context::class.java)
|
val instance = hmsClass.getMethod("getInstance", Context::class.java)
|
||||||
.invoke(null, context)
|
.invoke(null, context)
|
||||||
val appId = getAppId(context)
|
val appId = config.huaweiAppId.ifBlank { getAppId(context) }
|
||||||
if (appId.isNotBlank()) {
|
if (appId.isNotBlank()) {
|
||||||
val token = hmsClass.getMethod(
|
val token = hmsClass.getMethod(
|
||||||
"getToken",
|
"getToken",
|
||||||
|
|||||||
@ -6,21 +6,11 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.xuqm.sdk.push.PushSDK
|
import com.xuqm.sdk.push.PushSDK
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
import com.xuqm.sdk.push.model.PushVendor
|
||||||
|
import com.xuqm.sdk.push.model.PushVendorConfig
|
||||||
import java.lang.reflect.Proxy
|
import java.lang.reflect.Proxy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OPPO 推送集成框架
|
* OPPO 推送集成。OPPO Push 依赖由 sdk-push 自身声明,注册参数优先来自租户 PUSH 配置。
|
||||||
*
|
|
||||||
* 需要添加 OPPO Push SDK 依赖:
|
|
||||||
* ```groovy
|
|
||||||
* implementation 'com.heytap.mcs:push:3.x.x'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* 在 `AndroidManifest.xml` 的 `<application>` 下配置:
|
|
||||||
* ```xml
|
|
||||||
* <meta-data android:name="XUQM_OPPO_APP_KEY" android:value="xxxxxxxx" />
|
|
||||||
* <meta-data android:name="XUQM_OPPO_APP_SECRET" android:value="xxxxxxxx" />
|
|
||||||
* ```
|
|
||||||
*/
|
*/
|
||||||
class OppoPushService : PushVendorInterface {
|
class OppoPushService : PushVendorInterface {
|
||||||
|
|
||||||
@ -28,49 +18,26 @@ class OppoPushService : PushVendorInterface {
|
|||||||
|
|
||||||
override fun isAvailable(context: Context): Boolean {
|
override fun isAvailable(context: Context): Boolean {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
Class.forName("com.heytap.mcssdk.PushManager")
|
resolvePushManagerClass()
|
||||||
true
|
true
|
||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun register(context: Context) {
|
override fun register(context: Context, config: PushVendorConfig) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
|
|
||||||
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
|
|
||||||
|
|
||||||
val meta = context.packageManager.getApplicationInfo(
|
val meta = context.packageManager.getApplicationInfo(
|
||||||
context.packageName, PackageManager.GET_META_DATA
|
context.packageName, PackageManager.GET_META_DATA
|
||||||
).metaData ?: Bundle.EMPTY
|
).metaData ?: Bundle.EMPTY
|
||||||
val appKey = meta.getString("XUQM_OPPO_APP_KEY", "")
|
val appKey = config.oppoAppKey.ifBlank { meta.getString("XUQM_OPPO_APP_KEY", "") }
|
||||||
val appSecret = meta.getString("XUQM_OPPO_APP_SECRET", "")
|
val appSecret = config.oppoAppSecret.ifBlank { meta.getString("XUQM_OPPO_APP_SECRET", "") }
|
||||||
|
|
||||||
if (appKey.isNotBlank() && appSecret.isNotBlank()) {
|
if (appKey.isNotBlank() && appSecret.isNotBlank()) {
|
||||||
val callbackClass = Class.forName("com.heytap.mcssdk.callback.PushCallback")
|
if (!registerWithHeytapMsp(context, appKey, appSecret)) {
|
||||||
val proxy = Proxy.newProxyInstance(
|
registerWithLegacyMcs(context, appKey, appSecret)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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")
|
Log.i(TAG, "OPPO push registration requested")
|
||||||
} else {
|
} 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 ->
|
}.onFailure { error ->
|
||||||
Log.w(TAG, "OPPO push registration failed: ${error.message}")
|
Log.w(TAG, "OPPO push registration failed: ${error.message}")
|
||||||
@ -79,10 +46,13 @@ class OppoPushService : PushVendorInterface {
|
|||||||
|
|
||||||
override fun unregister(context: Context) {
|
override fun unregister(context: Context) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val pushManagerClass = Class.forName("com.heytap.mcssdk.PushManager")
|
val pushManagerClass = resolvePushManagerClass()
|
||||||
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
|
if (pushManagerClass.name == "com.heytap.msp.push.HeytapPushManager") {
|
||||||
pushManagerClass.getMethod("unRegister", Context::class.java)
|
pushManagerClass.getMethod("unRegister").invoke(null)
|
||||||
.invoke(instance, context)
|
} else {
|
||||||
|
val instance = pushManagerClass.getMethod("getInstance").invoke(null)
|
||||||
|
pushManagerClass.getMethod("unRegister", Context::class.java).invoke(instance, context)
|
||||||
|
}
|
||||||
Log.i(TAG, "OPPO push unregistered")
|
Log.i(TAG, "OPPO push unregistered")
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
Log.w(TAG, "OPPO push unregistration failed: ${error.message}")
|
Log.w(TAG, "OPPO push unregistration failed: ${error.message}")
|
||||||
@ -91,5 +61,90 @@ class OppoPushService : PushVendorInterface {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "OppoPushService"
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.xuqm.sdk.push.vendor
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
import com.xuqm.sdk.push.model.PushVendor
|
||||||
|
import com.xuqm.sdk.push.model.PushVendorConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 厂商推送服务接口
|
* 厂商推送服务接口
|
||||||
@ -22,7 +23,7 @@ interface PushVendorInterface {
|
|||||||
/**
|
/**
|
||||||
* 注册厂商推送服务,触发获取推送 Token 的流程。
|
* 注册厂商推送服务,触发获取推送 Token 的流程。
|
||||||
*/
|
*/
|
||||||
fun register(context: Context)
|
fun register(context: Context, config: PushVendorConfig = PushVendorConfig())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注销厂商推送服务。
|
* 注销厂商推送服务。
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import android.content.Context
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.xuqm.sdk.push.PushSDK
|
import com.xuqm.sdk.push.PushSDK
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
import com.xuqm.sdk.push.model.PushVendor
|
||||||
|
import com.xuqm.sdk.push.model.PushVendorConfig
|
||||||
|
import java.lang.reflect.Proxy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vivo 推送集成框架
|
* vivo 推送集成框架
|
||||||
@ -31,13 +33,33 @@ class VivoPushService : PushVendorInterface {
|
|||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun register(context: Context) {
|
override fun register(context: Context, config: PushVendorConfig) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val pushClientClass = Class.forName("com.vivo.push.PushClient")
|
val pushClientClass = Class.forName("com.vivo.push.PushClient")
|
||||||
val instance = pushClientClass.getMethod("getInstance", Context::class.java)
|
val instance = pushClientClass.getMethod("getInstance", Context::class.java)
|
||||||
.invoke(null, context)
|
.invoke(null, context)
|
||||||
// PushClient.getInstance(context).initialize()
|
|
||||||
pushClientClass.getMethod("initialize").invoke(instance)
|
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")
|
Log.i(TAG, "Vivo push registration requested")
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
Log.w(TAG, "Vivo push registration failed: ${error.message}")
|
Log.w(TAG, "Vivo push registration failed: ${error.message}")
|
||||||
@ -59,4 +81,18 @@ class VivoPushService : PushVendorInterface {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VivoPushService"
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,26 +6,13 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.xuqm.sdk.push.PushSDK
|
import com.xuqm.sdk.push.PushSDK
|
||||||
import com.xuqm.sdk.push.model.PushVendor
|
import com.xuqm.sdk.push.model.PushVendor
|
||||||
|
import com.xuqm.sdk.push.model.PushVendorConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小米推送集成框架
|
* 小米推送集成框架
|
||||||
*
|
*
|
||||||
* 需要添加 MiPush SDK 依赖:
|
* MiPush AAR 需要随 sdk-push 发布。注册参数优先来自租户 PUSH 配置,
|
||||||
* ```groovy
|
* 旧版本 manifest meta-data 仅作为兼容兜底。
|
||||||
* implementation 'com.xiaomi.mipush:mipush:5.x.x'
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* 在 `AndroidManifest.xml` 的 `<application>` 下配置:
|
|
||||||
* ```xml
|
|
||||||
* <meta-data android:name="XUQM_XIAOMI_APP_ID" android:value="288230376xxxxxxxx" />
|
|
||||||
* <meta-data android:name="XUQM_XIAOMI_APP_KEY" android:value="xxxxxxxxxxxx" />
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* 并在自定义 `PushMessageReceiver` 的 `onReceiveRegisterResult()` 回调中获取 token,
|
|
||||||
* 然后调用:
|
|
||||||
* ```kotlin
|
|
||||||
* PushSDK.updateNativePushToken(context, PushVendor.XIAOMI, token)
|
|
||||||
* ```
|
|
||||||
*/
|
*/
|
||||||
class XiaomiPushService : PushVendorInterface {
|
class XiaomiPushService : PushVendorInterface {
|
||||||
|
|
||||||
@ -38,14 +25,14 @@ class XiaomiPushService : PushVendorInterface {
|
|||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun register(context: Context) {
|
override fun register(context: Context, config: PushVendorConfig) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val miPushClass = Class.forName("com.xiaomi.mipush.sdk.MiPushClient")
|
val miPushClass = Class.forName("com.xiaomi.mipush.sdk.MiPushClient")
|
||||||
val meta = context.packageManager.getApplicationInfo(
|
val meta = context.packageManager.getApplicationInfo(
|
||||||
context.packageName, PackageManager.GET_META_DATA
|
context.packageName, PackageManager.GET_META_DATA
|
||||||
).metaData ?: Bundle.EMPTY
|
).metaData ?: Bundle.EMPTY
|
||||||
val appId = meta.getString("XUQM_XIAOMI_APP_ID", "")
|
val appId = config.xiaomiAppId.ifBlank { meta.getString("XUQM_XIAOMI_APP_ID", "") }
|
||||||
val appKey = meta.getString("XUQM_XIAOMI_APP_KEY", "")
|
val appKey = config.xiaomiAppKey.ifBlank { meta.getString("XUQM_XIAOMI_APP_KEY", "") }
|
||||||
if (appId.isNotBlank() && appKey.isNotBlank()) {
|
if (appId.isNotBlank() && appKey.isNotBlank()) {
|
||||||
miPushClass.getMethod(
|
miPushClass.getMethod(
|
||||||
"registerPush",
|
"registerPush",
|
||||||
@ -54,8 +41,9 @@ class XiaomiPushService : PushVendorInterface {
|
|||||||
String::class.java,
|
String::class.java,
|
||||||
).invoke(null, context, appId, appKey)
|
).invoke(null, context, appId, appKey)
|
||||||
Log.i(TAG, "Xiaomi push registration requested")
|
Log.i(TAG, "Xiaomi push registration requested")
|
||||||
|
pollRegId(context, miPushClass)
|
||||||
} else {
|
} 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 ->
|
}.onFailure { error ->
|
||||||
Log.w(TAG, "Xiaomi push registration failed: ${error.message}")
|
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 {
|
companion object {
|
||||||
private const val TAG = "XiaomiPushService"
|
private const val TAG = "XiaomiPushService"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String>
|
||||||
|
}.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
maven(url = "https://nexus.xuqinmin.com/repository/android/")
|
maven(url = "https://nexus.xuqinmin.com/repository/android/")
|
||||||
|
maven(url = "https://developer.hihonor.com/repo")
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
@ -11,6 +12,7 @@ dependencyResolutionManagement {
|
|||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
maven(url = "https://nexus.xuqinmin.com/repository/android/")
|
maven(url = "https://nexus.xuqinmin.com/repository/android/")
|
||||||
|
maven(url = "https://developer.hihonor.com/repo")
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户