package com.xuqm.sdk.push import android.content.Context import android.os.Build import android.util.Log import com.google.firebase.messaging.FirebaseMessaging import com.xuqm.sdk.XuqmLoginSession 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.model.PushRegistrationSnapshot import com.xuqm.sdk.push.model.PushVendor import com.xuqm.sdk.push.storage.PushRegistrationStore import com.xuqm.sdk.push.vendor.FcmPushService import com.xuqm.sdk.push.vendor.HonorPushService import com.xuqm.sdk.push.vendor.HuaweiPushService import com.xuqm.sdk.push.vendor.OppoPushService import com.xuqm.sdk.push.vendor.PushVendorInterface import com.xuqm.sdk.push.vendor.VivoPushService import com.xuqm.sdk.push.vendor.XiaomiPushService import com.xuqm.sdk.utils.DeviceUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.util.concurrent.atomic.AtomicReference object PushSDK { private val api: PushApi get() = ApiClient.create(PushApi::class.java, ServiceEndpointRegistry.pushBaseUrl) private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val registeredUserId = AtomicReference(null) fun currentRegistration(context: Context): PushRegistrationSnapshot? { XuqmSDK.requireInit() val deviceId = DeviceUtils.getDeviceId(context) return store(context).load(deviceId = deviceId, fallbackVendor = detectVendor()) } fun updateNativePushToken( context: Context, vendor: PushVendor, pushToken: String, ) { XuqmSDK.requireInit() val normalizedToken = pushToken.trim() require(normalizedToken.isNotBlank()) { "pushToken must not be blank" } store(context).save(vendor, normalizedToken) val sessionUserId = XuqmSDK.currentLoginSession?.userId if (sessionUserId != null) { bindImUser(context, sessionUserId) } } fun bindImUser(context: Context, userId: String) { if (!isReceivePushEnabled(context)) return ensureNativePushToken(context) registerDevice(context, userId) } fun unbindImUser(userId: String) { unregisterDevice(userId) } fun setReceivePush( context: Context, userId: String? = XuqmSDK.currentLoginSession?.userId, enabled: Boolean, ) { XuqmSDK.requireInit() store(context).setReceivePush(enabled) val resolvedUserId = userId ?: registeredUserId.get() if (resolvedUserId != null) { scope.launch { runCatching { api.setReceivePush( appId = XuqmSDK.appKey, userId = resolvedUserId, enabled = enabled, ) if (enabled) { bindImUser(context, resolvedUserId) } } } } } fun registerDevice( context: Context, userId: String, ) { XuqmSDK.requireInit() val registration = currentRegistration(context) val vendor = registration?.vendor ?: detectVendor() val pushToken = registration?.pushToken?.takeIf { it.isNotBlank() } if (pushToken.isNullOrBlank()) { Log.w("XuqmPushSDK", "Native push token not ready yet, waiting for onNewToken()") ensureNativePushToken(context) return } scope.launch { runCatching { api.registerDevice( appId = XuqmSDK.appKey, userId = userId, vendor = vendor.name, token = pushToken, ) registeredUserId.set(userId) store(context).updateLastUserId(userId) Log.i( "XuqmPushSDK", "Registered push device for userId=$userId vendor=${vendor.name}", ) } } } fun unregisterDevice(userId: String) { XuqmSDK.requireInit() scope.launch { runCatching { val reg = store(XuqmSDK.appContext).load( deviceId = DeviceUtils.getDeviceId(XuqmSDK.appContext), fallbackVendor = detectVendor(), ) api.unregisterDevice(XuqmSDK.appKey, userId, reg?.vendor?.name ?: detectVendor().name) registeredUserId.compareAndSet(userId, null) store(XuqmSDK.appContext).updateLastUserId(null) } } } private val vendorServices: List = listOf( HuaweiPushService(), XiaomiPushService(), OppoPushService(), VivoPushService(), HonorPushService(), FcmPushService(), ) fun detectVendor(): PushVendor { return when (Build.MANUFACTURER.uppercase()) { "HUAWEI" -> PushVendor.HUAWEI "XIAOMI" -> PushVendor.XIAOMI "OPPO" -> PushVendor.OPPO "VIVO" -> PushVendor.VIVO "HONOR" -> PushVendor.HONOR else -> PushVendor.FCM } } 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) } } if (detectedVendor == PushVendor.FCM) { ensureNativePushToken(context) } } fun onSdkLogin(session: XuqmLoginSession) { val context = runCatching { XuqmSDK.appContext }.getOrNull() ?: return if (registeredUserId.get() == session.userId) return initializeVendors(context) bindImUser(context, session.userId) } fun onSdkLogout() { val userId = registeredUserId.getAndSet(null) ?: return unregisterDevice(userId) } private fun store(context: Context): PushRegistrationStore = PushRegistrationStore(context.applicationContext) private fun isReceivePushEnabled(context: Context): Boolean = store(context).load( deviceId = DeviceUtils.getDeviceId(context), fallbackVendor = detectVendor(), )?.receivePush ?: true private fun ensureNativePushToken(context: Context) { val vendor = detectVendor() val registration = currentRegistration(context) if (registration?.pushToken?.isNotBlank() == true) return if (vendor == PushVendor.FCM) { runCatching { FirebaseMessaging.getInstance() } .onSuccess { messaging -> messaging.token .addOnSuccessListener { token -> if (token.isNotBlank()) { store(context).save(PushVendor.FCM, token) val sessionUserId = XuqmSDK.currentLoginSession?.userId if (sessionUserId != null) { registerDevice(context, sessionUserId) } } } .addOnFailureListener { error -> Log.w("XuqmPushSDK", "Unable to fetch FCM token: ${error.message}") } } .onFailure { error -> Log.w("XuqmPushSDK", "Firebase Messaging not available: ${error.message}") } } else { val service = vendorServices.firstOrNull { it.vendor == vendor } if (service != null && service.isAvailable(context)) { service.register(context) } else { Log.w( "XuqmPushSDK", "Vendor ${vendor.name} service not available, falling back to FCM", ) runCatching { FirebaseMessaging.getInstance() } .onSuccess { messaging -> messaging.token .addOnSuccessListener { token -> if (token.isNotBlank()) { store(context).save(PushVendor.FCM, token) val sessionUserId = XuqmSDK.currentLoginSession?.userId if (sessionUserId != null) { registerDevice(context, sessionUserId) } } } .addOnFailureListener { error -> Log.w("XuqmPushSDK", "Unable to fetch FCM token: ${error.message}") } } .onFailure { error -> Log.w("XuqmPushSDK", "Firebase Messaging not available: ${error.message}") } } } } }