Skip to content

Commit

Permalink
Add GDPR and CCPA consent APIs to Chartboost Flutter Adapter Plugin
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 702353803
  • Loading branch information
LTPhantom committed Jan 2, 2025
1 parent 406add0 commit 5735a6c
Show file tree
Hide file tree
Showing 12 changed files with 651 additions and 41 deletions.
7 changes: 3 additions & 4 deletions packages/mediation/gma_mediation_chartboost/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

#### 1.0.0

TODO: Add version numbers
* Initial release.
* Verified compatibility with Chartboost Android adapter version a.b.c.d
* Verified compatibility with Chartboost iOS adapter version a.b.c.d
* Built and tested with the Google Mobile Ads Flutter Plugin version X.Y.Z.
* Verified compatibility with Chartboost Android adapter version 9.8.1.0
* Verified compatibility with Chartboost iOS adapter version 9.8.0.0
* Built and tested with the Google Mobile Ads Flutter Plugin version 5.2.0.
22 changes: 16 additions & 6 deletions packages/mediation/gma_mediation_chartboost/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,29 @@ android {

dependencies {
implementation("com.google.ads.mediation:chartboost:9.8.1.0")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.6.1'
testImplementation 'androidx.test:core-ktx:1.6.1'
testImplementation 'androidx.test.ext:junit:1.2.1'
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.21'
testImplementation 'org.mockito:mockito-core:5.5.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.1.0'
testImplementation 'org.robolectric:robolectric:4.10.3'
}

testOptions {
unitTests.all {
useJUnitPlatform()
useJUnit()

testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
unitTests {
includeAndroidResources = true
unitTests.returnDefaultValues = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Autogenerated from Pigeon (v22.6.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost

import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}

private fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(exception.code, exception.message, exception.details)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception),
)
}
}

/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
*
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError(
val code: String,
override val message: String? = null,
val details: Any? = null,
) : Throwable()

private open class ChartboostSDKApiPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}

override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}

/**
* The generated classes set the channels to call the methods in the corresponding kotlin
* ChartboostSDKApi interface and swift ChartboostSDKApi protocol from the dart layer.
*
* Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
interface ChartboostSDKApi {
/** Used to configure GDPR consent on the Android or iOS Chartboost SDK */
fun setGDPRConsent(userConsent: Boolean)

/** Used to opt out of the sale of personal information in Chartboost SDK. */
fun setCCPAConsent(userOptIn: Boolean)

companion object {
/** The codec used by ChartboostSDKApi. */
val codec: MessageCodec<Any?> by lazy { ChartboostSDKApiPigeonCodec() }

/**
* Sets up an instance of `ChartboostSDKApi` to handle messages through the `binaryMessenger`.
*/
@JvmOverloads
fun setUp(
binaryMessenger: BinaryMessenger,
api: ChartboostSDKApi?,
messageChannelSuffix: String = "",
) {
val separatedMessageChannelSuffix =
if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.setGDPRConsent$separatedMessageChannelSuffix",
codec,
)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val userConsentArg = args[0] as Boolean
val wrapped: List<Any?> =
try {
api.setGDPRConsent(userConsentArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.setCCPAConsent$separatedMessageChannelSuffix",
codec,
)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val userOptInArg = args[0] as Boolean
val wrapped: List<Any?> =
try {
api.setCCPAConsent(userOptInArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,45 @@

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost

import android.content.Context
import com.chartboost.sdk.Chartboost
import com.chartboost.sdk.privacy.model.CCPA
import com.chartboost.sdk.privacy.model.GDPR
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding

/** Required to link the Android dependency of the Chartboost Adapter. */
class GmaMediationChartboostPlugin: FlutterPlugin {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { }
class GmaMediationChartboostPlugin : FlutterPlugin, ActivityAware, ChartboostSDKApi {
private lateinit var context: Context

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { }
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
ChartboostSDKApi.setUp(flutterPluginBinding.binaryMessenger, this)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
ChartboostSDKApi.setUp(binding.binaryMessenger, null)
}

override fun setGDPRConsent(userConsent: Boolean) {
val dataUserConsent =
if (userConsent) GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)
else GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL)
Chartboost.addDataUseConsent(context, dataUserConsent)
}

override fun setCCPAConsent(userOptIn: Boolean) {
val dataUseConsent =
if (userOptIn) CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE) else CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE)
Chartboost.addDataUseConsent(context, dataUseConsent)
}

override fun onDetachedFromActivity() {}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {}

override fun onDetachedFromActivityForConfigChanges() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,88 @@

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost

internal class GmaMediationChartboostPluginTest {}
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.chartboost.sdk.Chartboost
import com.chartboost.sdk.privacy.model.CCPA
import com.chartboost.sdk.privacy.model.GDPR
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock

@RunWith(AndroidJUnit4::class)
internal class GmaMediationChartboostPluginTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private val mockBinaryMessenger = mock<BinaryMessenger>()
private val mockFlutterPluginBinding =
mock<FlutterPlugin.FlutterPluginBinding> {
on { applicationContext } doReturn context
on { binaryMessenger } doReturn mockBinaryMessenger
}

@Test
fun setGDPRConsent_withTrueValue_addsDataUserConsentAsBehavioral() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)

plugin.setGDPRConsent(true)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun setGDPRConsent_withFalseValue_addsDataUserConsentAsNonBehavioral() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL)

plugin.setGDPRConsent(false)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun setCCPAConsent_withTrueValue_addsDataUserConsentAsOptInSale() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE)

plugin.setCCPAConsent(true)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun setCCPAConsent_withFalseValue_addsDataUserConsentAsOptOutSale() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE)

plugin.setCCPAConsent(false)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import UIKit
import XCTest

@testable import gma_mediation_chartboost

class GmaMediationChartboostPluginTests: XCTestCase {
func testSetGDPRConsent() {
let chartboostApiFake = ChartboostApiFake()

GmaMediationChartboostPlugin(chartboostApi: chartboostApiFake).setGDPRConsent(gdprConsent: true)

XCTAssertTrue(chartboostApiFake.gdprConsent)
}

func testSetCCPAConsent() {
let chartboostApiFake = ChartboostApiFake()

GmaMediationChartboostPlugin(chartboostApi: chartboostApiFake).setCCPAConsent(ccpaConsent: true)

XCTAssertTrue(chartboostApiFake.ccpaConsent)
}
}

class ChartboostApiFake: ChartboostApiProtocol {
var gdprConsent: Bool = false
var ccpaConsent: Bool = false

func setGDPRConsent(gdprConsent: Bool) {
self.gdprConsent = gdprConsent
}

func setCCPAConsent(ccpaConsent: Bool) {
self.ccpaConsent = ccpaConsent
}
}
Loading

0 comments on commit 5735a6c

Please sign in to comment.