package com.tinivo.rewardedads import android.app.Activity import com.applovin.mediation.MaxAd import com.applovin.mediation.MaxError import com.applovin.mediation.MaxReward import com.applovin.mediation.ads.MaxRewardedAd import com.applovin.mediation.ads.MaxRewardedAdListener import com.applovin.sdk.AppLovinSdk import com.cocos.lib.CocosHelper import com.cocos.lib.JsbBridgeWrapper import org.json.JSONObject class RewardedAdsMaxBridge( private val activity: Activity, ) : JsbBridgeWrapper.OnScriptEventListener, MaxRewardedAdListener { companion object { private const val REQUEST_EVENT = "tinivo.rewarded.request" private const val RESPONSE_EVENT = "tinivo.rewarded.response" private const val CONFIG_ASSET_PATH = "rewarded-provider-config.json" } private val rewardedAds = mutableMapOf() private val placementByAdUnitId = mutableMapOf() private val preloadRequestByPlacement = mutableMapOf() private val presentRequestByPlacement = mutableMapOf() private val rewardedPlacements = mutableSetOf() private val config by lazy { RewardedAdsMaxConfig.load(activity, CONFIG_ASSET_PATH) } private var installed = false fun install() { if (installed) { return } installed = true val sdk = AppLovinSdk.getInstance(activity) sdk.settings.mediationProvider = "max" sdk.initializeSdk { } JsbBridgeWrapper.getInstance().addScriptEventListener(REQUEST_EVENT, this) } override fun onScriptEvent(arg: String?) { val payload = safePayload(arg) val command = payload?.optString("command").orEmpty() val requestId = payload?.optString("requestId").orEmpty() val placement = payload?.optString("placement").orEmpty() when (command) { "preload" -> preload(requestId, placement) "show" -> show(requestId, placement) "reset" -> reset(placement) else -> dispatchError(requestId, placement, "unknown", "Unknown MAX rewarded command") } } private fun preload(requestId: String, placement: String) { val adUnitId = config.adUnitIdForPlacement(placement) if (adUnitId.isNullOrEmpty()) { dispatchError(requestId, placement, "configInvalid", "Missing MAX ad unit for $placement") return } placementByAdUnitId[adUnitId] = placement preloadRequestByPlacement[placement] = requestId val rewardedAd = rewardedAds.getOrPut(placement) { MaxRewardedAd.getInstance(adUnitId, activity).also { ad -> ad.setListener(this) } } rewardedAd.loadAd() } private fun show(requestId: String, placement: String) { val rewardedAd = rewardedAds[placement] if (rewardedAd == null || !rewardedAd.isReady) { dispatchError(requestId, placement, "notReady", "MAX rewarded ad is not ready for $placement") return } rewardedPlacements.remove(placement) presentRequestByPlacement[placement] = requestId rewardedAd.showAd() } private fun reset(placement: String) { if (placement.isBlank()) { rewardedAds.clear() placementByAdUnitId.clear() preloadRequestByPlacement.clear() presentRequestByPlacement.clear() rewardedPlacements.clear() return } rewardedAds.remove(placement) preloadRequestByPlacement.remove(placement) presentRequestByPlacement.remove(placement) rewardedPlacements.remove(placement) placementByAdUnitId.entries.removeAll { it.value == placement } } override fun onAdLoaded(ad: MaxAd) { val placement = placementByAdUnitId[ad.adUnitId] ?: return val requestId = preloadRequestByPlacement.remove(placement).orEmpty() dispatchResponse( JSONObject().apply { put("requestId", requestId) put("command", "preload") put("placement", placement) }, ) } override fun onAdLoadFailed(adUnitId: String, error: MaxError) { val placement = placementByAdUnitId[adUnitId].orEmpty() val requestId = preloadRequestByPlacement.remove(placement).orEmpty() dispatchError(requestId, placement, "loadFailed", error.message ?: "MAX rewarded ad failed to load") } override fun onAdDisplayed(ad: MaxAd) { } override fun onAdDisplayFailed(ad: MaxAd, error: MaxError) { val placement = placementByAdUnitId[ad.adUnitId].orEmpty() val requestId = presentRequestByPlacement.remove(placement).orEmpty() dispatchError(requestId, placement, "showFailed", error.message ?: "MAX rewarded ad failed to show") } override fun onAdClicked(ad: MaxAd) { } override fun onAdHidden(ad: MaxAd) { val placement = placementByAdUnitId[ad.adUnitId].orEmpty() val requestId = presentRequestByPlacement.remove(placement).orEmpty() val earned = rewardedPlacements.remove(placement) dispatchResponse( JSONObject().apply { put("requestId", requestId) put("command", "show") put("placement", placement) put("result", JSONObject().apply { put("completed", true) put("cancelled", !earned) put("earned", earned) }) }, ) rewardedAds[placement]?.loadAd() } override fun onUserRewarded(ad: MaxAd, reward: MaxReward) { val placement = placementByAdUnitId[ad.adUnitId] ?: return rewardedPlacements.add(placement) } private fun safePayload(arg: String?): JSONObject? { return try { if (arg.isNullOrBlank()) null else JSONObject(arg) } catch (_: Throwable) { null } } private fun dispatchError(requestId: String, placement: String, code: String, message: String) { dispatchResponse( JSONObject().apply { put("requestId", requestId) put("command", "error") put("placement", placement) put("error", JSONObject().apply { put("code", code) put("message", message) }) }, ) } private fun dispatchResponse(payload: JSONObject) { val content = payload.toString() CocosHelper.runOnGameThread { JsbBridgeWrapper.getInstance().dispatchEventToScript(RESPONSE_EVENT, content) } } } private data class RewardedAdsMaxConfig( val sdkKey: String, val placements: Map, ) { fun adUnitIdForPlacement(placement: String): String? = placements[placement] companion object { fun load(activity: Activity, assetPath: String): RewardedAdsMaxConfig { val json = activity.assets.open(assetPath).bufferedReader().use { it.readText() } val root = JSONObject(json) val shared = root.getJSONObject("shared") val placementsJson = root.getJSONObject("placements") val placements = mutableMapOf() for (key in placementsJson.keys()) { placements[key] = placementsJson.getJSONObject(key).getString("adUnitId") } return RewardedAdsMaxConfig( sdkKey = shared.getString("sdkKey"), placements = placements, ) } } }