Skip to content

Commit

Permalink
Added Privacy API implementation for DT Exchange Flutter Adapter (#1056)
Browse files Browse the repository at this point in the history
* Added DT Exchange Privacy API usage

* Added Android and iOS jobs to the DT Exchange github action

* Updated iOS versions to run swift tests
  • Loading branch information
LTPhantom authored Jun 20, 2024
1 parent fe544bf commit 0baba2e
Show file tree
Hide file tree
Showing 18 changed files with 965 additions and 64 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/gma_mediation_dtexchange.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,57 @@ on:
- main

jobs:
android:
runs-on: macos-latest
if: github.event_name == 'pull_request'
timeout-minutes: 30
steps:
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
with:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: "Install Flutter"
run: ./.github/workflows/scripts/install-flutter.sh stable
- name: "Install Tools"
run: ./.github/workflows/scripts/install-tools.sh
- name: "Build Example"
run: ./.github/workflows/scripts/build-example.sh android ./lib/main.dart packages/mediation/gma_mediation_dtexchange/example
- name: "Unit Tests"
run: |
cd packages/mediation/gma_mediation_dtexchange/example/android
./gradlew :gma_mediation_dtexchange:testDebugUnitTest
iOS:
runs-on: macos-latest
timeout-minutes: 40
steps:
- uses: swift-actions/setup-swift@v2
with:
swift-version: "5.7.2"
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633
with:
fetch-depth: 0
- name: "Install Flutter"
run: ./.github/workflows/scripts/install-flutter.sh stable
- name: "Install Tools"
run: |
./.github/workflows/scripts/install-tools.sh
- name: "Unit Tests"
run: |
cd packages/mediation/gma_mediation_dtexchange/example/ios
flutter clean
flutter pub get
flutter precache --ios
pod install
xcodebuild -configuration Debug -resultBundlePath TestResults VERBOSE_SCRIPT_LOGGING=YES -workspace Runner.xcworkspace -scheme Runner -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' test
- uses: actions/upload-artifact@v4
if: failure()
with:
name: iOSTestResults
path: packages/mediation/gma_mediation_dtexchange/example/ios/TestResults.xcresult
flutter:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
Expand Down
22 changes: 16 additions & 6 deletions packages/mediation/gma_mediation_dtexchange/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,29 @@ android {

dependencies {
implementation 'com.google.ads.mediation:fyber:8.2.7.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.5.0'
testImplementation 'androidx.test:core-ktx:1.5.0'
testImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.20'
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,139 @@
// Autogenerated from Pigeon (v19.0.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_dtexchange

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()
/**
* The generated classes set the channels to call the methods in the corresponding kotlin DTExchangePrivacyApi interface and swift DTExchangePrivacyApi protocol from the dart layer.
*
* Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
interface DTExchangePrivacyApi {
/** Used to configure LGDP on the Android or iOS DTExchange SDK. */
fun setLgpdConsent(wasConsentGiven: Boolean)
/** Used to clear the LGDP flag on the Android or iOS DTExchange SDK. */
fun clearLgpdConsentData()
/** Used to configure consent to Sell Personal Information on the Android or iOS DTExchange SDK. */
fun setUSPrivacyString(usPrivacyString: String)
/** Used to clear the US Privacy flag on the Android or iOS DTExchange SDK. */
fun clearUSPrivacyString()

companion object {
/** The codec used by DTExchangePrivacyApi. */
val codec: MessageCodec<Any?> by lazy {
StandardMessageCodec()
}
/** Sets up an instance of `DTExchangePrivacyApi` to handle messages through the `binaryMessenger`. */
fun setUp(binaryMessenger: BinaryMessenger, api: DTExchangePrivacyApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_dtexchange.DTExchangePrivacyApi.setLgpdConsent$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val wasConsentGivenArg = args[0] as Boolean
val wrapped: List<Any?> = try {
api.setLgpdConsent(wasConsentGivenArg)
listOf<Any?>(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_dtexchange.DTExchangePrivacyApi.clearLgpdConsentData$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.clearLgpdConsentData()
listOf<Any?>(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_dtexchange.DTExchangePrivacyApi.setUSPrivacyString$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val usPrivacyStringArg = args[0] as String
val wrapped: List<Any?> = try {
api.setUSPrivacyString(usPrivacyStringArg)
listOf<Any?>(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_dtexchange.DTExchangePrivacyApi.clearUSPrivacyString$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.clearUSPrivacyString()
listOf<Any?>(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
package io.flutter.plugins.googlemobileads.mediation.gma_mediation_dtexchange

import com.fyber.inneractive.sdk.external.InneractiveAdManager
import io.flutter.embedding.engine.plugins.FlutterPlugin

/** Class that serves as bridge to get the adapter android dependency and make it available to a Flutter app. */
class GmaMediationDTExchangePlugin: FlutterPlugin {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {}
/** Manages DTExchangePrivacyApi and implements the needed methods. */
class GmaMediationDTExchangePlugin: FlutterPlugin, DTExchangePrivacyApi {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
DTExchangePrivacyApi.setUp(flutterPluginBinding.binaryMessenger, this)
}

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

override fun setLgpdConsent(wasConsentGiven: Boolean) {
InneractiveAdManager.setLgpdConsent(wasConsentGiven)
}

override fun clearLgpdConsentData() {
InneractiveAdManager.clearLgpdConsentData()
}

override fun setUSPrivacyString(usPrivacyString: String) {
InneractiveAdManager.setUSPrivacyString(usPrivacyString)
}

override fun clearUSPrivacyString() {
InneractiveAdManager.clearUSPrivacyString()
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,80 @@
package io.flutter.plugins.googlemobileads.mediation.gma_mediation_dtexchange

import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import org.mockito.Mockito

/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fyber.inneractive.sdk.external.InneractiveAdManager
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.eq

@RunWith(AndroidJUnit4::class)
internal class GmaMediationDtexchangePluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = GmaMediationDtexchangePlugin()
fun setLgpdConsent_withTrueValue_invokesSetLgpdConsentWithTrueValue() {
val plugin = GmaMediationDTExchangePlugin()
mockStatic(InneractiveAdManager::class.java).use { mockedDTExchangeAdManager ->

val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
plugin.setLgpdConsent(true)

Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
mockedDTExchangeAdManager.verify {
InneractiveAdManager.setLgpdConsent(eq(true))
}
}
}

@Test
fun setLgpdConsent_withFalseValue_invokesSetLgpdConsentWithFalseValue() {
val plugin = GmaMediationDTExchangePlugin()
mockStatic(InneractiveAdManager::class.java).use { mockedDTExchangeAdManager ->

plugin.setLgpdConsent(false)

mockedDTExchangeAdManager.verify {
InneractiveAdManager.setLgpdConsent(eq(false))
}
}
}

@Test
fun clearLgpdConsentData_invokesClearLgpdConsentData() {
val plugin = GmaMediationDTExchangePlugin()
mockStatic(InneractiveAdManager::class.java).use { mockedDTExchangeAdManager ->

plugin.clearLgpdConsentData()

mockedDTExchangeAdManager.verify {
InneractiveAdManager.clearLgpdConsentData()
}
}
}

@Test
fun setUSPrivacyString_invokesSetUSPrivacyString() {
val plugin = GmaMediationDTExchangePlugin()
mockStatic(InneractiveAdManager::class.java).use { mockedDTExchangeAdManager ->

plugin.setUSPrivacyString(TEST_CONSENT_STRING)

mockedDTExchangeAdManager.verify {
InneractiveAdManager.setUSPrivacyString(eq(TEST_CONSENT_STRING))
}
}
}

@Test
fun clearUSPrivacyString_invokesClearUSPrivacyString() {
val plugin = GmaMediationDTExchangePlugin()
mockStatic(InneractiveAdManager::class.java).use { mockedDTExchangeAdManager ->

plugin.clearUSPrivacyString()

mockedDTExchangeAdManager.verify {
InneractiveAdManager.clearUSPrivacyString()
}
}
}

companion object {
const val TEST_CONSENT_STRING = "testConsentString"
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
Loading

0 comments on commit 0baba2e

Please sign in to comment.