Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Privacy API implementation for DT Exchange Flutter Adapter #1056

Merged
merged 11 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 14 Pro,OS=16.2' 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
24 changes: 17 additions & 7 deletions packages/mediation/gma_mediation_dtexchange/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,30 @@ android {
}

dependencies {
implementation 'com.google.ads.mediation:fyber:8.2.6.1'
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
implementation 'com.google.ads.mediation:fyber:8.2.7.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,126 @@
// Autogenerated from Pigeon (v18.0.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon

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?> {
if (exception is FlutterError) {
return listOf(
exception.code,
exception.message,
exception.details
)
} else {
return 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 GDPR on the Android or iOS DTExchange SDK */
fun setGDPRConsent(gdprConsent: Boolean)
/** Used to configure GDPR String on the Android or iOS DTExchange SDK */
fun setGDPRConsentString(gdprConsentString: String)
/** Used to configure consent to Sell Personal Information on the Android or iOS DTExchange SDK */
fun setUSPrivacyString(usPrivacyString: String)

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`. */
@Suppress("UNCHECKED_CAST")
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.setGDPRConsent$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val gdprConsentArg = args[0] as Boolean
var wrapped: List<Any?>
try {
api.setGDPRConsent(gdprConsentArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_dtexchange.DTExchangePrivacyApi.setGDPRConsentString$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val gdprConsentStringArg = args[0] as String
var wrapped: List<Any?>
try {
api.setGDPRConsentString(gdprConsentStringArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = 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
var wrapped: List<Any?>
try {
api.setUSPrivacyString(usPrivacyStringArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
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 setGDPRConsent(gdprConsent: Boolean) {
InneractiveAdManager.setGdprConsent(gdprConsent)
}

override fun setGDPRConsentString(gdprConsentString: String) {
InneractiveAdManager.setGdprConsentString(gdprConsentString)
}

override fun setUSPrivacyString(usPrivacyString: String) {
InneractiveAdManager.setUSPrivacyString(usPrivacyString)
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,67 @@
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 setGDPRConsent_withTrueValue_invokesSetGdprConsentWithTrueValue() {
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.setGDPRConsent(true)

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

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

plugin.setGDPRConsent(false)

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

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

plugin.setGDPRConsentString(TEST_CONSENT_STRING)

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

@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))
}
}
}

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"
43 changes: 43 additions & 0 deletions packages/mediation/gma_mediation_dtexchange/example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
use_modular_headers!

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
Loading