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}" 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()) { fun getVendor(): String = when (Build.MANUFACTURER.lowercase()) {
"huawei", "honor" -> if (Build.MANUFACTURER.lowercase() == "honor") "HONOR" else "HUAWEI" "huawei", "honor" -> if (Build.MANUFACTURER.lowercase() == "honor") "HONOR" else "HUAWEI"
"xiaomi", "redmi" -> "XIAOMI" "xiaomi", "redmi" -> "XIAOMI"

查看文件

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

查看文件

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

查看文件

@ -41,10 +41,15 @@ class VivoPushService : PushVendorInterface {
// v4.x SDK: initialize(PushConfig) — the no-arg overload is private // v4.x SDK: initialize(PushConfig) — the no-arg overload is private
val builderClass = Class.forName("com.vivo.push.PushConfig\$Builder") val builderClass = Class.forName("com.vivo.push.PushConfig\$Builder")
val builder = builderClass.getConstructor().newInstance() val builder = builderClass.getConstructor().newInstance()
runCatching { val privacySet = runCatching {
builderClass.getMethod("agreePrivacyStatement", Boolean::class.javaPrimitiveType) builderClass.getMethod("agreePrivacyStatement", Boolean::class.javaPrimitiveType)
.invoke(builder, true) .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 pushConfig = builderClass.getMethod("build").invoke(builder)
val pushConfigClass = Class.forName("com.vivo.push.PushConfig") val pushConfigClass = Class.forName("com.vivo.push.PushConfig")
pushClientClass.getMethod("initialize", pushConfigClass).invoke(instance, pushConfig) pushClientClass.getMethod("initialize", pushConfigClass).invoke(instance, pushConfig)