Skip to content

Commit 3139fb7

Browse files
committed
Add GDPR and CCPA consent APIs to Chartboost Flutter Adapter Plugin
PiperOrigin-RevId: 702353803
1 parent 406add0 commit 3139fb7

File tree

12 files changed

+650
-41
lines changed

12 files changed

+650
-41
lines changed

packages/mediation/gma_mediation_chartboost/CHANGELOG.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
#### 1.0.0
44

5-
TODO: Add version numbers
65
* Initial release.
7-
* Verified compatibility with Chartboost Android adapter version a.b.c.d
8-
* Verified compatibility with Chartboost iOS adapter version a.b.c.d
9-
* Built and tested with the Google Mobile Ads Flutter Plugin version X.Y.Z.
6+
* Verified compatibility with Chartboost Android adapter version 9.8.1.0
7+
* Verified compatibility with Chartboost iOS adapter version 9.8.0.0
8+
* Built and tested with the Google Mobile Ads Flutter Plugin version 5.2.0.

packages/mediation/gma_mediation_chartboost/android/build.gradle

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,29 @@ android {
5454

5555
dependencies {
5656
implementation("com.google.ads.mediation:chartboost:9.8.1.0")
57-
testImplementation("org.jetbrains.kotlin:kotlin-test")
58-
testImplementation("org.mockito:mockito-core:5.0.0")
57+
testImplementation 'junit:junit:4.13.2'
58+
testImplementation 'androidx.test:core:1.6.1'
59+
testImplementation 'androidx.test:core-ktx:1.6.1'
60+
testImplementation 'androidx.test.ext:junit:1.2.1'
61+
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.21'
62+
testImplementation 'org.mockito:mockito-core:5.5.0'
63+
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.1.0'
64+
testImplementation 'org.robolectric:robolectric:4.10.3'
5965
}
6066

6167
testOptions {
6268
unitTests.all {
63-
useJUnitPlatform()
69+
useJUnit()
6470

6571
testLogging {
66-
events "passed", "skipped", "failed", "standardOut", "standardError"
67-
outputs.upToDateWhen {false}
68-
showStandardStreams = true
72+
events "passed", "skipped", "failed", "standardOut", "standardError"
73+
outputs.upToDateWhen {false}
74+
showStandardStreams = true
6975
}
7076
}
77+
unitTests {
78+
includeAndroidResources = true
79+
unitTests.returnDefaultValues = true
80+
}
7181
}
7282
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Autogenerated from Pigeon (v22.6.2), do not edit directly.
2+
// See also: https://pub.dev/packages/pigeon
3+
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
4+
5+
package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost
6+
7+
import android.util.Log
8+
import io.flutter.plugin.common.BasicMessageChannel
9+
import io.flutter.plugin.common.BinaryMessenger
10+
import io.flutter.plugin.common.MessageCodec
11+
import io.flutter.plugin.common.StandardMessageCodec
12+
import java.io.ByteArrayOutputStream
13+
import java.nio.ByteBuffer
14+
15+
private fun wrapResult(result: Any?): List<Any?> {
16+
return listOf(result)
17+
}
18+
19+
private fun wrapError(exception: Throwable): List<Any?> {
20+
return if (exception is FlutterError) {
21+
listOf(exception.code, exception.message, exception.details)
22+
} else {
23+
listOf(
24+
exception.javaClass.simpleName,
25+
exception.toString(),
26+
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception),
27+
)
28+
}
29+
}
30+
31+
/**
32+
* Error class for passing custom error details to Flutter via a thrown PlatformException.
33+
*
34+
* @property code The error code.
35+
* @property message The error message.
36+
* @property details The error details. Must be a datatype supported by the api codec.
37+
*/
38+
class FlutterError(
39+
val code: String,
40+
override val message: String? = null,
41+
val details: Any? = null,
42+
) : Throwable()
43+
44+
private open class ChartboostSDKApiPigeonCodec : StandardMessageCodec() {
45+
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
46+
return super.readValueOfType(type, buffer)
47+
}
48+
49+
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
50+
super.writeValue(stream, value)
51+
}
52+
}
53+
54+
/**
55+
* The generated classes set the channels to call the methods in the corresponding kotlin
56+
* ChartboostSDKApi interface and swift ChartboostSDKApi protocol from the dart layer.
57+
*
58+
* Generated interface from Pigeon that represents a handler of messages from Flutter.
59+
*/
60+
interface ChartboostSDKApi {
61+
/** Used to configure GDPR consent on the Android or iOS Chartboost SDK */
62+
fun setGDPRConsent(userConsent: Boolean)
63+
64+
/** Used to opt out of the sale of personal information in Chartboost SDK. */
65+
fun setCCPAConsent(userOptIn: Boolean)
66+
67+
companion object {
68+
/** The codec used by ChartboostSDKApi. */
69+
val codec: MessageCodec<Any?> by lazy { ChartboostSDKApiPigeonCodec() }
70+
71+
/**
72+
* Sets up an instance of `ChartboostSDKApi` to handle messages through the `binaryMessenger`.
73+
*/
74+
@JvmOverloads
75+
fun setUp(
76+
binaryMessenger: BinaryMessenger,
77+
api: ChartboostSDKApi?,
78+
messageChannelSuffix: String = "",
79+
) {
80+
val separatedMessageChannelSuffix =
81+
if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
82+
run {
83+
val channel =
84+
BasicMessageChannel<Any?>(
85+
binaryMessenger,
86+
"dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.setGDPRConsent$separatedMessageChannelSuffix",
87+
codec,
88+
)
89+
if (api != null) {
90+
channel.setMessageHandler { message, reply ->
91+
val args = message as List<Any?>
92+
val userConsentArg = args[0] as Boolean
93+
val wrapped: List<Any?> =
94+
try {
95+
api.setGDPRConsent(userConsentArg)
96+
listOf(null)
97+
} catch (exception: Throwable) {
98+
wrapError(exception)
99+
}
100+
reply.reply(wrapped)
101+
}
102+
} else {
103+
channel.setMessageHandler(null)
104+
}
105+
}
106+
run {
107+
val channel =
108+
BasicMessageChannel<Any?>(
109+
binaryMessenger,
110+
"dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.setCCPAConsent$separatedMessageChannelSuffix",
111+
codec,
112+
)
113+
if (api != null) {
114+
channel.setMessageHandler { message, reply ->
115+
val args = message as List<Any?>
116+
val userOptInArg = args[0] as Boolean
117+
val wrapped: List<Any?> =
118+
try {
119+
api.setCCPAConsent(userOptInArg)
120+
listOf(null)
121+
} catch (exception: Throwable) {
122+
wrapError(exception)
123+
}
124+
reply.reply(wrapped)
125+
}
126+
} else {
127+
channel.setMessageHandler(null)
128+
}
129+
}
130+
}
131+
}
132+
}

packages/mediation/gma_mediation_chartboost/android/src/main/kotlin/io/flutter/plugins/googlemobileads/mediation/gma_mediation_chartboost/GmaMediationChartboostPlugin.kt

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,45 @@
1414

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

17+
import android.content.Context
18+
import com.chartboost.sdk.Chartboost
19+
import com.chartboost.sdk.privacy.model.CCPA
20+
import com.chartboost.sdk.privacy.model.GDPR
1721
import io.flutter.embedding.engine.plugins.FlutterPlugin
22+
import io.flutter.embedding.engine.plugins.activity.ActivityAware
23+
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
1824

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

23-
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { }
29+
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
30+
context = flutterPluginBinding.applicationContext
31+
ChartboostSDKApi.setUp(flutterPluginBinding.binaryMessenger, this)
32+
}
33+
34+
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
35+
ChartboostSDKApi.setUp(binding.binaryMessenger, null)
36+
}
37+
38+
override fun setGDPRConsent(userConsent: Boolean) {
39+
val dataUserConsent =
40+
if (userConsent) GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)
41+
else GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL)
42+
Chartboost.addDataUseConsent(context, dataUserConsent)
43+
}
44+
45+
override fun setCCPAConsent(userOptIn: Boolean) {
46+
val dataUseConsent =
47+
if (userOptIn) CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE) else CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE)
48+
Chartboost.addDataUseConsent(context, dataUseConsent)
49+
}
50+
51+
override fun onDetachedFromActivity() {}
52+
53+
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}
54+
55+
override fun onAttachedToActivity(binding: ActivityPluginBinding) {}
56+
57+
override fun onDetachedFromActivityForConfigChanges() {}
2458
}

packages/mediation/gma_mediation_chartboost/android/src/test/kotlin/io/flutter/plugins/googlemobileads/mediation/gma_mediation_chartboost/GmaMediationChartboostPluginTest.kt

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,88 @@
1414

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

17-
internal class GmaMediationChartboostPluginTest {}
17+
import android.content.Context
18+
import androidx.test.core.app.ApplicationProvider
19+
import androidx.test.ext.junit.runners.AndroidJUnit4
20+
import com.chartboost.sdk.Chartboost
21+
import com.chartboost.sdk.privacy.model.CCPA
22+
import com.chartboost.sdk.privacy.model.GDPR
23+
import io.flutter.embedding.engine.plugins.FlutterPlugin
24+
import io.flutter.plugin.common.BinaryMessenger
25+
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
import org.mockito.Mockito.mockStatic
28+
import org.mockito.kotlin.doReturn
29+
import org.mockito.kotlin.eq
30+
import org.mockito.kotlin.mock
31+
32+
@RunWith(AndroidJUnit4::class)
33+
internal class GmaMediationChartboostPluginTest {
34+
private val context = ApplicationProvider.getApplicationContext<Context>()
35+
private val mockBinaryMessenger = mock<BinaryMessenger>()
36+
private val mockFlutterPluginBinding =
37+
mock<FlutterPlugin.FlutterPluginBinding> {
38+
on { applicationContext } doReturn context
39+
on { binaryMessenger } doReturn mockBinaryMessenger
40+
}
41+
42+
@Test
43+
fun setGDPRConsent_withTrueValue_addsDataUserConsentAsBehavioral() {
44+
val plugin = GmaMediationChartboostPlugin()
45+
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
46+
plugin.onAttachedToEngine(mockFlutterPluginBinding)
47+
val expectedDataUserConsent = GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)
48+
49+
plugin.setGDPRConsent(true)
50+
51+
mockedchartboostSDKApi.verify {
52+
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
53+
}
54+
}
55+
}
56+
57+
@Test
58+
fun setGDPRConsent_withFalseValue_addsDataUserConsentAsNonBehavioral() {
59+
val plugin = GmaMediationChartboostPlugin()
60+
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
61+
plugin.onAttachedToEngine(mockFlutterPluginBinding)
62+
val expectedDataUserConsent = GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL)
63+
64+
plugin.setGDPRConsent(false)
65+
66+
mockedchartboostSDKApi.verify {
67+
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
68+
}
69+
}
70+
}
71+
72+
@Test
73+
fun setCCPAConsent_withTrueValue_addsDataUserConsentAsOptInSale() {
74+
val plugin = GmaMediationChartboostPlugin()
75+
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
76+
plugin.onAttachedToEngine(mockFlutterPluginBinding)
77+
val expectedDataUserConsent = CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE)
78+
79+
plugin.setCCPAConsent(true)
80+
81+
mockedchartboostSDKApi.verify {
82+
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
83+
}
84+
}
85+
}
86+
87+
@Test
88+
fun setCCPAConsent_withFalseValue_addsDataUserConsentAsOptOutSale() {
89+
val plugin = GmaMediationChartboostPlugin()
90+
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
91+
plugin.onAttachedToEngine(mockFlutterPluginBinding)
92+
val expectedDataUserConsent = CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE)
93+
94+
plugin.setCCPAConsent(false)
95+
96+
mockedchartboostSDKApi.verify {
97+
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
98+
}
99+
}
100+
}
101+
}

packages/mediation/gma_mediation_chartboost/example/ios/RunnerTests/GmaMediationChartboostPluginTest.swift

Lines changed: 0 additions & 20 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import UIKit
16+
import XCTest
17+
18+
@testable import gma_mediation_chartboost
19+
20+
class GmaMediationChartboostPluginTests: XCTestCase {
21+
func testSetGDPRConsent() {
22+
let chartboostApiFake = ChartboostApiFake()
23+
24+
GmaMediationChartboostPlugin(chartboostApi: chartboostApiFake).setGDPRConsent(gdprConsent: true)
25+
26+
XCTAssertTrue(chartboostApiFake.gdprConsent)
27+
}
28+
29+
func testSetCCPAConsent() {
30+
let chartboostApiFake = ChartboostApiFake()
31+
32+
GmaMediationChartboostPlugin(chartboostApi: chartboostApiFake).setCCPAConsent(ccpaConsent: true)
33+
34+
XCTAssertTrue(chartboostApiFake.ccpaConsent)
35+
}
36+
}
37+
38+
class ChartboostApiFake: ChartboostApiProtocol {
39+
var gdprConsent: Bool = false
40+
var ccpaConsent: Bool = false
41+
42+
func setGDPRConsent(gdprConsent: Bool) {
43+
self.gdprConsent = gdprConsent
44+
}
45+
46+
func setCCPAConsent(ccpaConsent: Bool) {
47+
self.ccpaConsent = ccpaConsent
48+
}
49+
}

0 commit comments

Comments
 (0)