feat(sdk): 新增 ROM 版本上报及 VIVO 隐私协议日志

- DeviceUtils.getRomVersion() 通过反射读取厂商 ROM 版本(MIUI/EMUI/OriginOS/ColorOS/MagicOS)
- PushApi/PushSDK: 注册时上报 romVersion 字段
- VivoPushService: 记录 agreePrivacyStatement 是否成功的明确日志,便于诊断

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-26 12:21:17 +08:00
父节点 edbabeb578
当前提交 7063d102a2
共有 4 个文件被更改,包括 39 次插入2 次删除

查看文件

@ -15,6 +15,29 @@ object DeviceUtils {
fun getOsVersion(): String = "Android ${Build.VERSION.RELEASE}"
/**
* 读取厂商 ROM 版本号MIUI / EMUI / OriginOS / ColorOS / MagicOS
* 通过反射读取系统属性失败时返回 null
*/
fun getRomVersion(): String? {
val props = listOf(
"ro.miui.ui.version.name", // MIUI
"ro.build.version.emui", // EMUI / HarmonyOS
"ro.vivo.os.version", // OriginOS (VIVO)
"ro.funtouch.os_version", // FuntouchOS (旧 VIVO)
"ro.color.os.version", // ColorOS (OPPO)
"ro.build.version.opporom", // ColorOS 旧版
"ro.build.version.magic_os", // MagicOS (Honor)
)
return try {
val cls = Class.forName("android.os.SystemProperties")
val get = cls.getMethod("get", String::class.java, String::class.java)
props.firstNotNullOfOrNull { key ->
(get.invoke(null, key, "") as? String)?.takeIf { it.isNotBlank() }
}
} catch (_: Exception) { null }
}
fun getVendor(): String = when (Build.MANUFACTURER.lowercase()) {
"huawei", "honor" -> if (Build.MANUFACTURER.lowercase() == "honor") "HONOR" else "HUAWEI"
"xiaomi", "redmi" -> "XIAOMI"

查看文件

@ -135,6 +135,7 @@ object PushSDK {
fun onSdkLogin(session: XuqmLoginSession) {
val context = runCatching { XuqmSDK.appContext }.getOrNull() ?: return
Log.d("XuqmPushSDK", "onSdkLogin: userId=${session.userId} registeredUserId=${registeredUserId.get()}")
if (registeredUserId.get() == session.userId) return
initializeVendorsInternal(context)
bindImUserInternal(context, session.userId)
@ -150,6 +151,7 @@ object PushSDK {
// ── Internal push registration flow ───────────────────────────────────────
internal fun updateNativePushToken(context: Context, vendor: PushVendor, pushToken: String) {
Log.i("XuqmPushSDK", "updateNativePushToken called: vendor=$vendor token=${pushToken.take(12)}...")
XuqmSDK.requireInit()
val detectedVendor = detectVendor()
if (vendor != detectedVendor) {
@ -160,6 +162,7 @@ object PushSDK {
require(normalizedToken.isNotBlank()) { "pushToken must not be blank" }
store(context).save(vendor, normalizedToken)
val sessionUserId = XuqmSDK.getUserId()
Log.i("XuqmPushSDK", "updateNativePushToken: saved token, sessionUserId=$sessionUserId")
if (sessionUserId != null) {
bindImUserInternal(context, sessionUserId)
}
@ -220,6 +223,7 @@ object PushSDK {
}
private fun bindImUserInternal(context: Context, userId: String) {
Log.i("XuqmPushSDK", "bindImUserInternal: userId=$userId receivePushEnabled=${isReceivePushEnabled(context)}")
if (!isReceivePushEnabled(context)) return
ensureNativePushTokenInternal(context)
registerDeviceInternal(context, userId)
@ -238,7 +242,7 @@ object PushSDK {
}
val registrationKey = listOf(userId, vendor.name, pushToken, deviceId).joinToString("|")
if (lastRegisteredDeviceKey.get() == registrationKey) {
Log.d("XuqmPushSDK", "Skipping duplicate push device registration for userId=$userId vendor=${vendor.name}")
Log.i("XuqmPushSDK", "Skipping duplicate push device registration for userId=$userId vendor=${vendor.name}")
return
}
if (!registeringDeviceKey.compareAndSet(null, registrationKey)) {
@ -249,6 +253,7 @@ object PushSDK {
}
scope.launch {
runCatching {
Log.i("XuqmPushSDK", "Registering push device: userId=$userId vendor=${vendor.name} token=${pushToken.take(12)}...")
api.registerDevice(
appKey = XuqmSDK.appKey,
userId = userId,
@ -259,11 +264,14 @@ object PushSDK {
model = DeviceUtils.getDeviceModel(),
osVersion = DeviceUtils.getOsVersion(),
appVersion = appVersion(context),
romVersion = DeviceUtils.getRomVersion(),
)
registeredUserId.set(userId)
lastRegisteredDeviceKey.set(registrationKey)
store(context).updateLastUserId(userId)
Log.i("XuqmPushSDK", "Registered push device for userId=$userId vendor=${vendor.name}")
}.onFailure { e ->
Log.e("XuqmPushSDK", "Push device registration failed for userId=$userId: ${e.message}", e)
}.also {
registeringDeviceKey.compareAndSet(registrationKey, null)
}

查看文件

@ -18,6 +18,7 @@ interface PushApi {
@Query("osVersion") osVersion: String,
@Query("platform") platform: String = "ANDROID",
@Query("appVersion") appVersion: String? = null,
@Query("romVersion") romVersion: String? = null,
)
@DELETE("api/push/unregister")

查看文件

@ -41,10 +41,15 @@ class VivoPushService : PushVendorInterface {
// v4.x SDK: initialize(PushConfig) — the no-arg overload is private
val builderClass = Class.forName("com.vivo.push.PushConfig\$Builder")
val builder = builderClass.getConstructor().newInstance()
runCatching {
val privacySet = runCatching {
builderClass.getMethod("agreePrivacyStatement", Boolean::class.javaPrimitiveType)
.invoke(builder, true)
true
}.getOrElse { e ->
Log.w(TAG, "agreePrivacyStatement not available in this SDK version: ${e.message}")
false
}
Log.i(TAG, "Vivo push PushConfig built: agreePrivacyStatement=$privacySet")
val pushConfig = builderClass.getMethod("build").invoke(builder)
val pushConfigClass = Class.forName("com.vivo.push.PushConfig")
pushClientClass.getMethod("initialize", pushConfigClass).invoke(instance, pushConfig)