diff --git a/.gitignore b/.gitignore index 90405ee3..c89f86b4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ captures testServerAddress.txt app/bin unity-ads/bin +libs javadoc .settings .project diff --git a/Makefile b/Makefile index b53cf00c..d8ad7131 100644 --- a/Makefile +++ b/Makefile @@ -37,16 +37,16 @@ test-local-staging-localhost: device-connected wake-up-device push-test-server-a run-all-tests: test-instrumentation test-legacy test-integration test-ci: - ./gradlew connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.InstrumentationTestSuite,com.unity3d.ads.test.LegacyTestSuite + ./gradlew unity-ads:connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.InstrumentationTestSuite,com.unity3d.ads.test.LegacyTestSuite test-instrumentation: - ./gradlew connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.InstrumentationTestSuite + ./gradlew unity-ads:connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.InstrumentationTestSuite test-legacy: - ./gradlew connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.LegacyTestSuite + ./gradlew unity-ads:connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.LegacyTestSuite test-integration: - ./gradlew connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.IntegrationTestSuite + ./gradlew unity-ads:connectedDebugAndroidTest -i -w --stacktrace -Pandroid.testInstrumentationRunnerArguments.class=com.unity3d.ads.test.IntegrationTestSuite push-test-server-address-ip: echo http://$(shell ifconfig |grep "inet" |grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" |grep -v -E "^0|^127" -m 1):8080 > testServerAddress.txt diff --git a/app/build.gradle b/app/build.gradle index 7558337e..c046dc01 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.unity3d.ads.example" minSdkVersion 19 targetSdkVersion 30 - versionCode = 3720 - versionName = "3.7.2" + versionCode = 3740 + versionName = "3.7.4" } flavorDimensions "arEnabled" @@ -27,18 +27,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 - } } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.0.0' - implementation 'com.google.android.material:material:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + implementation "com.google.android.material:material:1.3.0" + implementation "androidx.appcompat:appcompat:1.3.0" + implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" arImplementation 'com.google.ar:core:1.4.0' + implementation 'androidx.transition:transition:1.4.1' implementation project(':unity-ads') } diff --git a/app/src/main/res/drawable/unityads_logo.png b/app/src/main/res/drawable/unityads_logo.png index 6bae9c3d..818a1da7 100644 Binary files a/app/src/main/res/drawable/unityads_logo.png and b/app/src/main/res/drawable/unityads_logo.png differ diff --git a/build.gradle b/build.gradle index 06f0c209..767a89d1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,23 +1,22 @@ allprojects { repositories { google() - jcenter() + mavenCentral() } } buildscript { repositories { - jcenter() gradlePluginPortal() google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:4.2.1' classpath 'org.jacoco:org.jacoco.core:0.8.1' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.20.0' classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' } } -apply from: 'nexusPublishing.gradle' \ No newline at end of file +apply from: 'nexusPublishing.gradle' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 36ca9b1b..d2ccb675 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/settings.gradle b/settings.gradle index bbfbd457..c12076ba 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,5 @@ -include ':app', ':unity-ads' +include ':unity-scaradapter-1920' +include ':unity-scaradapter-1950' +include ':unity-scaradapter-2000' +include ':unity-scaradapter-common' +include ':app', ':unity-ads' \ No newline at end of file diff --git a/unity-ads/build.gradle b/unity-ads/build.gradle index cdd09ad1..54e3ee3d 100644 --- a/unity-ads/build.gradle +++ b/unity-ads/build.gradle @@ -19,8 +19,8 @@ dependencies { ext { GROUP_ID = "com.unity3d.ads" ARTIFACT_ID = "unity-ads" - VERSION_ID = "3.7.2" - VERSION_CODE = 3720 + VERSION_ID = "3.7.4" + VERSION_CODE = 3740 SIGN_AAR = properties.getProperty("SIGN_AAR") ?: false } @@ -47,6 +47,7 @@ android { buildConfigField('int', 'VERSION_CODE', "$versionCode") buildConfigField('String', 'VERSION_NAME', "\"$versionName\"") testBuildType "debug" + multiDexEnabled true testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunnerArguments disableAnalytics: 'true' // Won't work yet, see: https://code.google.com/p/android/issues/detail?id=188241 @@ -71,6 +72,25 @@ android { } } +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + androidTestImplementation project(':unity-scaradapter-2000') + androidTestImplementation project(':unity-scaradapter-1950') + androidTestImplementation project(':unity-scaradapter-1920') + androidTestImplementation project(':unity-scaradapter-common') + androidTestImplementation 'org.mockito:mockito-core:2.28.2' + androidTestImplementation 'org.mockito:mockito-android:2.25.0' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test:rules:1.3.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'com.google.android.gms:play-services-ads:19.5.0' + compileOnly 'com.google.ar:core:1.0.0' + compileOnly project(':unity-scaradapter-2000') + compileOnly project(':unity-scaradapter-1950') + compileOnly project(':unity-scaradapter-1920') + compileOnly project(':unity-scaradapter-common') +} + task javadoc(type: Javadoc) { description "Generates Javadoc for Release" source = android.sourceSets.main.java.srcDirs @@ -126,4 +146,4 @@ def getPropertyStringWithDefaultValue(String key, String defaultValue) { apply from: 'publishing.gradle' apply from: 'artifactory.gradle' -apply from: 'jacoco.gradle' \ No newline at end of file +apply from: 'jacoco.gradle' diff --git a/unity-ads/src/androidTest/AndroidManifest.xml b/unity-ads/src/androidTest/AndroidManifest.xml index 7ce788ec..b2435b8a 100644 --- a/unity-ads/src/androidTest/AndroidManifest.xml +++ b/unity-ads/src/androidTest/AndroidManifest.xml @@ -35,6 +35,10 @@ android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:hardwareAccelerated="true" /> + + \ No newline at end of file diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java index 5115f25d..58ea0f2a 100644 --- a/unity-ads/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java @@ -1,5 +1,6 @@ package com.unity3d.ads.test; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.GmaScarTestSuite; import com.unity3d.ads.test.instrumentation.services.ads.operation.AdOperationTests; import com.unity3d.ads.test.instrumentation.services.ads.operation.LoadModuleDecoratorInitializationBufferTests; import com.unity3d.ads.test.instrumentation.services.ads.operation.LoadModuleDecoratorTests; @@ -28,7 +29,6 @@ AcquisitionTypeTest.class, AdsPropertiesTests.class, InitializationNotificationCenterTest.class, -// LoadBridgeTest.class, WebPlayerViewSettingsCacheTest.class, WebPlayerViewCacheTest.class, BannerViewCacheTests.class, @@ -42,7 +42,8 @@ LoadModuleDecoratorInitializationBufferTests.class, AdOperationTests.class, ShowModuleTests.class, - ConfigurationTest.class + ConfigurationTest.class, + GmaScarTestSuite.class }) public class InstrumentationTestSuite {} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/GmaScarTestSuite.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/GmaScarTestSuite.java new file mode 100644 index 00000000..b0f6324a --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/GmaScarTestSuite.java @@ -0,0 +1,27 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar; + +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.adapters.ScarAdapterFactoryTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.finder.GMAInitializerTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.finder.PresenceDetectorTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.finder.ScarVersionFinderTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges.AdapterStatusBridgeTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges.InitializationStatusBridgeTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges.InitializeListenerBridgeTest; +import com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges.MobileAdsBridgeTest; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ScarAdapterFactoryTest.class, + GMAInitializerTest.class, + PresenceDetectorTest.class, + ScarVersionFinderTest.class, + AdapterStatusBridgeTest.class, + InitializationStatusBridgeTest.class, + InitializeListenerBridgeTest.class, + MobileAdsBridgeTest.class +}) +public class GmaScarTestSuite { +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/adapters/ScarAdapterFactoryTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/adapters/ScarAdapterFactoryTest.java new file mode 100644 index 00000000..e6cbafc0 --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/adapters/ScarAdapterFactoryTest.java @@ -0,0 +1,43 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.adapters; + +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarAdapter; +import com.unity3d.services.ads.gmascar.adapters.ScarAdapterFactory; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ScarAdapterFactoryTest { + @Mock + private IAdsErrorHandler adsErrorHandlerMock; + + private ScarAdapterFactory _scarAdapterFactory = new ScarAdapterFactory(); + + @Test + public void testScarAdapterFactory1920() { + IScarAdapter adapter = _scarAdapterFactory.createScarAdapter(ScarAdapterFactory.CODE_19_2, adsErrorHandlerMock); + Assert.assertTrue(adapter instanceof com.unity3d.scar.adapter.v1920.ScarAdapter); + } + + @Test + public void testScarAdapterFactory1950() { + IScarAdapter adapter = _scarAdapterFactory.createScarAdapter(ScarAdapterFactory.CODE_19_5, adsErrorHandlerMock); + Assert.assertTrue(adapter instanceof com.unity3d.scar.adapter.v1950.ScarAdapter); + } + + @Test + public void testScarAdapterFactory2000() { + IScarAdapter adapter = _scarAdapterFactory.createScarAdapter(ScarAdapterFactory.CODE_20_0, adsErrorHandlerMock); + Assert.assertTrue(adapter instanceof com.unity3d.scar.adapter.v2000.ScarAdapter); + } + + @Test + public void testScarAdapterFactoryUnsupported() { + IScarAdapter adapter = _scarAdapterFactory.createScarAdapter(-1, adsErrorHandlerMock); + Assert.assertNull(adapter); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/AdapterStatusBridgeTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/AdapterStatusBridgeTest.java new file mode 100644 index 00000000..cf8bc615 --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/AdapterStatusBridgeTest.java @@ -0,0 +1,19 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges; + +import com.google.android.gms.ads.initialization.AdapterStatus; +import com.unity3d.services.ads.gmascar.bridges.AdapterStatusBridge; + +import org.junit.Assert; +import org.junit.Test; + +public class AdapterStatusBridgeTest { + + @Test + public void testAdapterStatusBridge() { + AdapterStatusBridge adapterStatusBridge = new AdapterStatusBridge(); + Object[] statesEnum = adapterStatusBridge.getAdapterStatesEnum(); + Assert.assertEquals(AdapterStatus.State.values().length, statesEnum.length); + Assert.assertEquals(AdapterStatus.State.NOT_READY, statesEnum[0]); + Assert.assertEquals(AdapterStatus.State.READY, statesEnum[1]); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/InitializationStatusBridgeTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/InitializationStatusBridgeTest.java new file mode 100644 index 00000000..f4562b0f --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/InitializationStatusBridgeTest.java @@ -0,0 +1,18 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges; + +import com.unity3d.services.ads.gmascar.bridges.InitializationStatusBridge; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class InitializationStatusBridgeTest { + + @Test + public void testInitializationStatusBridgeNotInitialized() { + InitializationStatusBridge initializationStatusBridge = new InitializationStatusBridge(); + Map adapterStatusMap = initializationStatusBridge.getAdapterStatusMap(new Object()); + Assert.assertNull(adapterStatusMap); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/InitializeListenerBridgeTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/InitializeListenerBridgeTest.java new file mode 100644 index 00000000..79c5c2e4 --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/InitializeListenerBridgeTest.java @@ -0,0 +1,16 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges; + +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; + +import org.junit.Assert; +import org.junit.Test; + +public class InitializeListenerBridgeTest { + + @Test + public void testInitializeListenerBridge() { + InitializeListenerBridge initializeListenerBridge = new InitializeListenerBridge(); + Object listenerProxy = initializeListenerBridge.createInitializeListenerProxy(); + Assert.assertNotNull(listenerProxy); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/MobileAdsBridgeTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/MobileAdsBridgeTest.java new file mode 100644 index 00000000..7f07a8cc --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/bridges/MobileAdsBridgeTest.java @@ -0,0 +1,52 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.bridges; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.android.gms.ads.initialization.InitializationStatus; +import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.mockito.Mockito.timeout; + +public class MobileAdsBridgeTest { + + @Test + @Ignore("Have to ignore for now cause the bridge is stateful (underlying call to GMA static init depends on other tests") + public void testMobileAdsBridgeGetVersionNotInitialized() { + MobileAdsBridge mobileAdsBridge = new MobileAdsBridge(); + String versionString = mobileAdsBridge.getVersionString(); + Assert.assertEquals("0.0.0", versionString); + } + + @Test + public void testMobileAdsBridgeGetVersion() { + OnInitializationCompleteListener initializationCompleteListener = Mockito.mock(OnInitializationCompleteListener.class); + MobileAdsBridge mobileAdsBridge = new MobileAdsBridge(); + mobileAdsBridge.initialize(InstrumentationRegistry.getInstrumentation().getContext(), initializationCompleteListener); + Mockito.verify(initializationCompleteListener, timeout(1000).times(1)).onInitializationComplete(Mockito.any(InitializationStatus.class)); + String versionString = mobileAdsBridge.getVersionString(); + Assert.assertTrue(String.format("Minor version 203404000 is not found in %s", versionString), versionString.contains("203404000")); + } + + @Test + @Ignore("Cannot test this case since the underlying GMA call is static so test ordering impacts this result.") + public void testMobileAdsBridgeGetInitStatusNotInitialized() { + MobileAdsBridge mobileAdsBridge = new MobileAdsBridge(); + Object initializationStatus = mobileAdsBridge.getInitializationStatus(); + Assert.assertNull(initializationStatus); + } + + @Test + public void testMobileAdsBridgeGetInitStatus() { + OnInitializationCompleteListener initializationCompleteListener = Mockito.mock(OnInitializationCompleteListener.class); + MobileAdsBridge mobileAdsBridge = new MobileAdsBridge(); + mobileAdsBridge.initialize(InstrumentationRegistry.getInstrumentation().getContext(), initializationCompleteListener); + Object initializationStatus = mobileAdsBridge.getInitializationStatus(); + Assert.assertTrue(InitializationStatus.class.isAssignableFrom(initializationStatus.getClass())); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/GMAInitializerTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/GMAInitializerTest.java new file mode 100644 index 00000000..d53afcd8 --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/GMAInitializerTest.java @@ -0,0 +1,61 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.finder; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; +import com.unity3d.services.ads.gmascar.bridges.AdapterStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializationStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; +import com.unity3d.services.ads.gmascar.finder.GMAInitializer; +import com.unity3d.services.core.webview.WebView; +import com.unity3d.services.core.webview.WebViewApp; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +@RunWith(MockitoJUnitRunner.class) +public class GMAInitializerTest { + @Mock + MobileAdsBridge mobileAdsBridge; + @Mock + InitializeListenerBridge initializeListenerBridge; + @Mock + InitializationStatusBridge initializationStatusBridge; + @Mock + AdapterStatusBridge adapterStatusBridge; + + @Test + public void testGmaInitializer() { + GMAInitializer gmaInitializer = new GMAInitializer(mobileAdsBridge, initializeListenerBridge, initializationStatusBridge, adapterStatusBridge); + gmaInitializer.initializeGMA(); + Mockito.verify(mobileAdsBridge, times(1)).initialize(Mockito.any(Context.class), Mockito.any()); + } + + @Test + public void testGmaInitializerInitSuccess() { + MobileAdsBridge realMobileAdsBridge = new MobileAdsBridge(); + OnInitializationCompleteListener initializationCompleteListener = Mockito.mock(OnInitializationCompleteListener.class); + realMobileAdsBridge.initialize(InstrumentationRegistry.getInstrumentation().getContext(), initializationCompleteListener); + Object initializationStatus = realMobileAdsBridge.getInitializationStatus(); + + InitializationStatusBridge realStatusBridge = new InitializationStatusBridge(); + GMAInitializer gmaInitializer = new GMAInitializer(realMobileAdsBridge, initializeListenerBridge, realStatusBridge, adapterStatusBridge); + + WebViewApp mockWebViewApp = Mockito.mock(WebViewApp.class); + Mockito.when(mockWebViewApp.sendEvent(Mockito.any(Enum.class), Mockito.any(Enum.class))).thenReturn(true); + WebViewApp.setCurrentApp(mockWebViewApp); + Mockito.when(adapterStatusBridge.isGMAInitialized(Mockito.any())).thenReturn(true); + boolean initSuccess = gmaInitializer.initSuccessful(initializationStatus); + Assert.assertTrue(initSuccess); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/PresenceDetectorTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/PresenceDetectorTest.java new file mode 100644 index 00000000..450cc956 --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/PresenceDetectorTest.java @@ -0,0 +1,76 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.finder; + +import com.unity3d.services.ads.gmascar.bridges.AdapterStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializationStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; +import com.unity3d.services.ads.gmascar.finder.PresenceDetector; +import com.unity3d.services.ads.gmascar.finder.ScarVersionFinder; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PresenceDetectorTest { + @Mock + MobileAdsBridge mobileAdsBridgeMock; + @Mock + InitializeListenerBridge initializeListenerBridgeMock; + @Mock + InitializationStatusBridge initializationStatusBridgeMock; + @Mock + AdapterStatusBridge adapterStatusBridgeMock; + + @Before + public void setup() { + Mockito.when(mobileAdsBridgeMock.exists()).thenReturn(true); + Mockito.when(initializeListenerBridgeMock.exists()).thenReturn(true); + Mockito.when(initializationStatusBridgeMock.exists()).thenReturn(true); + Mockito.when(adapterStatusBridgeMock.exists()).thenReturn(true); + } + + @Test + public void testScarPresenceDetector() { + PresenceDetector scarPresenceDetector = new PresenceDetector(mobileAdsBridgeMock, initializeListenerBridgeMock, initializationStatusBridgeMock, adapterStatusBridgeMock); + Assert.assertTrue(scarPresenceDetector.areGMAClassesPresent()); + } + + @Test + public void testScarPresenceDetectorWithNullBridges() { + PresenceDetector scarPresenceDetector = new PresenceDetector(null, null, null, null); + Assert.assertFalse(scarPresenceDetector.areGMAClassesPresent()); + } + + @Test + public void testScarPresenceDetectorMobileAdsClassMissing() { + Mockito.when(mobileAdsBridgeMock.exists()).thenReturn(false); + PresenceDetector scarPresenceDetector = new PresenceDetector(mobileAdsBridgeMock, initializeListenerBridgeMock, initializationStatusBridgeMock, adapterStatusBridgeMock); + Assert.assertFalse(scarPresenceDetector.areGMAClassesPresent()); + } + + @Test + public void testScarPresenceDetectorListenerClassMissing() { + Mockito.when(initializeListenerBridgeMock.exists()).thenReturn(false); + PresenceDetector scarPresenceDetector = new PresenceDetector(mobileAdsBridgeMock, initializeListenerBridgeMock, initializationStatusBridgeMock, adapterStatusBridgeMock); + Assert.assertFalse(scarPresenceDetector.areGMAClassesPresent()); + } + + @Test + public void testScarPresenceDetectorInitStatusClassMissing() { + Mockito.when(initializationStatusBridgeMock.exists()).thenReturn(false); + PresenceDetector scarPresenceDetector = new PresenceDetector(mobileAdsBridgeMock, initializeListenerBridgeMock, initializationStatusBridgeMock, adapterStatusBridgeMock); + Assert.assertFalse(scarPresenceDetector.areGMAClassesPresent()); + } + + @Test + public void testScarPresenceDetectorAdapterStatusClassMissing() { + Mockito.when(adapterStatusBridgeMock.exists()).thenReturn(false); + PresenceDetector scarPresenceDetector = new PresenceDetector(mobileAdsBridgeMock, initializeListenerBridgeMock, initializationStatusBridgeMock, adapterStatusBridgeMock); + Assert.assertFalse(scarPresenceDetector.areGMAClassesPresent()); + } +} diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/ScarVersionFinderTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/ScarVersionFinderTest.java new file mode 100644 index 00000000..cd241ff3 --- /dev/null +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/instrumentation/services/ads/gmascar/finder/ScarVersionFinderTest.java @@ -0,0 +1,67 @@ +package com.unity3d.ads.test.instrumentation.services.ads.gmascar.finder; + +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; +import com.unity3d.services.ads.gmascar.finder.GMAInitializer; +import com.unity3d.services.ads.gmascar.finder.PresenceDetector; +import com.unity3d.services.ads.gmascar.finder.ScarVersionFinder; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ScarVersionFinderTest { + @Mock + MobileAdsBridge mobileAdsBridgeMock; + @Mock + PresenceDetector presenceDetector; + @Mock + GMAInitializer gmaInitializer; + + @Before + public void setup() { + Mockito.when(gmaInitializer.getInitializeListenerBridge()).thenReturn(Mockito.mock(InitializeListenerBridge.class)); + } + + @Test + public void testScarVersionFinder() { + Mockito.when(mobileAdsBridgeMock.getVersionString()).thenReturn("afma-sdk-a-v204890999.203404000.1"); + ScarVersionFinder scarVersionFinder = new ScarVersionFinder(mobileAdsBridgeMock, presenceDetector, gmaInitializer); + long versionCode = scarVersionFinder.getGoogleSdkVersionCode(); + Assert.assertEquals(203404000, versionCode); + } + + + @Test + public void testScarVersionFinderNullVersion() { + MobileAdsBridge mobileAdsBridgeMock = Mockito.mock(MobileAdsBridge.class); + Mockito.when(mobileAdsBridgeMock.getVersionString()).thenReturn(null); + ScarVersionFinder scarVersionFinder = new ScarVersionFinder(mobileAdsBridgeMock, presenceDetector, gmaInitializer); + long versionCode = scarVersionFinder.getGoogleSdkVersionCode(); + Assert.assertEquals(-1, versionCode); + } + + @Test + public void testScarVersionFinderMissingVersion() { + MobileAdsBridge mobileAdsBridgeMock = Mockito.mock(MobileAdsBridge.class); + Mockito.when(mobileAdsBridgeMock.getVersionString()).thenReturn(""); + ScarVersionFinder scarVersionFinder = new ScarVersionFinder(mobileAdsBridgeMock, presenceDetector, gmaInitializer); + long versionCode = scarVersionFinder.getGoogleSdkVersionCode(); + Assert.assertEquals(-1, versionCode); + } + + @Test + public void testScarVersionFinderInvalidVersion() { + MobileAdsBridge mobileAdsBridgeMock = Mockito.mock(MobileAdsBridge.class); + Mockito.when(mobileAdsBridgeMock.getVersionString()).thenReturn("invalid"); + ScarVersionFinder scarVersionFinder = new ScarVersionFinder(mobileAdsBridgeMock, presenceDetector, gmaInitializer); + long versionCode = scarVersionFinder.getGoogleSdkVersionCode(); + Assert.assertEquals(-1, versionCode); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/UnityServices.java b/unity-ads/src/main/java/com/unity3d/services/UnityServices.java index 299adf30..addfd528 100644 --- a/unity-ads/src/main/java/com/unity3d/services/UnityServices.java +++ b/unity-ads/src/main/java/com/unity3d/services/UnityServices.java @@ -180,6 +180,8 @@ public static boolean getDebugMode() { } private static String createExpectedParametersString(String fieldName, Object current, Object received) { - return "\n - " + fieldName + " Current: " + current.toString() + " | Received: " + received.toString(); + String currentSafeString = current == null ? "null" : current.toString(); + String receivedSafeString = received == null ? "null" : received.toString(); + return "\n - " + fieldName + " Current: " + currentSafeString + " | Received: " + receivedSafeString; } } diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/api/GMAScar.java b/unity-ads/src/main/java/com/unity3d/services/ads/api/GMAScar.java new file mode 100644 index 00000000..b9afb716 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/api/GMAScar.java @@ -0,0 +1,57 @@ +package com.unity3d.services.ads.api; + +import com.unity3d.services.ads.gmascar.GMAScarAdapterBridge; +import com.unity3d.services.core.webview.bridge.WebViewCallback; +import com.unity3d.services.core.webview.bridge.WebViewExposed; + +import org.json.JSONArray; +import org.json.JSONException; + +public class GMAScar { + + private static GMAScarAdapterBridge gmaScarAdapterBridge = new GMAScarAdapterBridge(); + + @WebViewExposed + public static void initializeScar(final WebViewCallback callback) { + gmaScarAdapterBridge.initializeScar(); + callback.invoke(); + } + + @WebViewExposed + public static void getVersion(final WebViewCallback callback) { + gmaScarAdapterBridge.getVersion(); + callback.invoke(); + } + + public static void isInitialized(final WebViewCallback callback) { + gmaScarAdapterBridge.isInitialized(); + callback.invoke(); + } + + @WebViewExposed + public static void getSCARSignals(final JSONArray interstitialList, final JSONArray rewardedList, final WebViewCallback callback) throws JSONException { + gmaScarAdapterBridge.getSCARSignals(getPlacementList(interstitialList), getPlacementList(rewardedList)); + callback.invoke(); + } + + @WebViewExposed + public static void load(final String placementId, final String queryId, final Boolean canSkip, final String adUnitId, final String adString, final Integer videoLengthMs, final WebViewCallback callback) { + gmaScarAdapterBridge.load(canSkip, placementId, queryId, adString, adUnitId, videoLengthMs); + callback.invoke(); + } + + @WebViewExposed + public static void show(final String placementId, final String queryId, final Boolean canSkip, final WebViewCallback callback) { + gmaScarAdapterBridge.show(placementId, queryId, canSkip); + callback.invoke(); + } + + private static String[] getPlacementList(JSONArray placements) throws JSONException { + String[] placementIdList = new String[placements.length()]; + for (int placementIndex = 0; placementIndex < placements.length(); placementIndex++) { + placementIdList[placementIndex] = placements.getString(placementIndex); + } + return placementIdList; + } + +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/configuration/AdsModuleConfiguration.java b/unity-ads/src/main/java/com/unity3d/services/ads/configuration/AdsModuleConfiguration.java index 60207ba1..ed7e7ada 100644 --- a/unity-ads/src/main/java/com/unity3d/services/ads/configuration/AdsModuleConfiguration.java +++ b/unity-ads/src/main/java/com/unity3d/services/ads/configuration/AdsModuleConfiguration.java @@ -4,10 +4,10 @@ import com.unity3d.ads.IUnityAdsListener; import com.unity3d.ads.UnityAds; +import com.unity3d.ads.properties.AdsProperties; import com.unity3d.services.ads.UnityAdsImplementation; import com.unity3d.services.ads.adunit.AdUnitOpen; import com.unity3d.services.ads.placement.Placement; -import com.unity3d.ads.properties.AdsProperties; import com.unity3d.services.ads.token.TokenStorage; import com.unity3d.services.core.configuration.Configuration; import com.unity3d.services.core.log.DeviceLog; @@ -32,7 +32,8 @@ public Class[] getWebAppApiClassList() { com.unity3d.services.ads.api.Purchasing.class, com.unity3d.services.ads.api.Load.class, com.unity3d.services.ads.api.Show.class, - com.unity3d.services.ads.api.Token.class + com.unity3d.services.ads.api.Token.class, + com.unity3d.services.ads.api.GMAScar.class }; return list; diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/GMAScarAdapterBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/GMAScarAdapterBridge.java new file mode 100644 index 00000000..c4f99a20 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/GMAScarAdapterBridge.java @@ -0,0 +1,120 @@ +package com.unity3d.services.ads.gmascar; + +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.scar.adapter.common.IScarAdapter; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.services.ads.gmascar.adapters.ScarAdapterFactory; +import com.unity3d.services.ads.gmascar.bridges.AdapterStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializationStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; +import com.unity3d.services.ads.gmascar.finder.GMAInitializer; +import com.unity3d.services.ads.gmascar.finder.PresenceDetector; +import com.unity3d.services.ads.gmascar.finder.ScarVersionFinder; +import com.unity3d.services.ads.gmascar.handlers.ScarInterstitialAdHandler; +import com.unity3d.services.ads.gmascar.handlers.ScarRewardedAdHandler; +import com.unity3d.services.ads.gmascar.handlers.SignalsHandler; +import com.unity3d.services.ads.gmascar.handlers.WebViewErrorHandler; +import com.unity3d.services.core.properties.ClientProperties; + +/** + * Adapter bridge that uses the Scar Adapter module based on the version of the GMA SDK in the developer's game. + */ +public class GMAScarAdapterBridge { + + private IScarAdapter _scarAdapter; + private MobileAdsBridge _mobileAdsBridge; + private ScarVersionFinder _scarVersionFinder; + + private InitializeListenerBridge _initializationListenerBridge; + private InitializationStatusBridge _initializationStatusBridge; + private AdapterStatusBridge _adapterStatusBridge; + private PresenceDetector _presenceDetector; + private GMAInitializer _gmaInitializer; + private WebViewErrorHandler _webViewErrorHandler; + private ScarAdapterFactory _scarAdapterFactory; + + public GMAScarAdapterBridge() { + _mobileAdsBridge = new MobileAdsBridge(); + _initializationStatusBridge = new InitializationStatusBridge(); + _initializationListenerBridge = new InitializeListenerBridge(); + _adapterStatusBridge = new AdapterStatusBridge(); + _webViewErrorHandler = new WebViewErrorHandler(); + _scarAdapterFactory = new ScarAdapterFactory(); + _presenceDetector = new PresenceDetector(_mobileAdsBridge, _initializationListenerBridge, _initializationStatusBridge, _adapterStatusBridge); + _gmaInitializer = new GMAInitializer(_mobileAdsBridge, _initializationListenerBridge, _initializationStatusBridge, _adapterStatusBridge); + _scarVersionFinder = new ScarVersionFinder(_mobileAdsBridge, _presenceDetector, _gmaInitializer); + } + + public void initializeScar() { + if (_presenceDetector.areGMAClassesPresent()) { + _gmaInitializer.initializeGMA(); + } else { + _webViewErrorHandler.handleError(new GMAAdsError(GMAEvent.INIT_ERROR)); + } + } + + public boolean isInitialized() { + return _gmaInitializer.isInitialized(); + } + + public void getVersion() { + _scarVersionFinder.getVersion(); + } + + public void getSCARSignals(String[] interstitialList, String[] rewardedList) { + _scarAdapter = getScarAdapterObject(); + SignalsHandler signalListener = new SignalsHandler(); + + if (_scarAdapter != null) { + _scarAdapter.getSCARSignals(ClientProperties.getApplicationContext(), interstitialList, rewardedList, signalListener); + } else { + _webViewErrorHandler.handleError(GMAAdsError.InternalSignalsError("Could not create SCAR adapter object")); + } + } + + public void load(final boolean canSkip, final String placementId, final String queryId, final String adString, final String adUnitId, final int videoLengthMs) { + ScarAdMetadata scarAdMetadata = new ScarAdMetadata(placementId, queryId, adUnitId, adString, videoLengthMs); + _scarAdapter = getScarAdapterObject(); + if (_scarAdapter != null) { + if (canSkip) { + loadInterstitialAd(scarAdMetadata); + } else { + loadRewardedAd(scarAdMetadata); + } + } else { + _webViewErrorHandler.handleError(GMAAdsError.InternalLoadError(scarAdMetadata, "Scar Adapter object is null")); + } + } + + private void loadInterstitialAd(final ScarAdMetadata scarAdMetadata) { + ScarInterstitialAdHandler adListener = new ScarInterstitialAdHandler(scarAdMetadata); + _scarAdapter.loadInterstitialAd(ClientProperties.getApplicationContext(), scarAdMetadata, adListener); + } + + private void loadRewardedAd(final ScarAdMetadata scarAdMetadata) { + ScarRewardedAdHandler adListener = new ScarRewardedAdHandler(scarAdMetadata); + _scarAdapter.loadRewardedAd(ClientProperties.getApplicationContext(), scarAdMetadata, adListener); + } + + public void show(final String placementId, final String queryId, final boolean canSkip) { + ScarAdMetadata scarAdMetadata = new ScarAdMetadata(placementId, queryId); + _scarAdapter = getScarAdapterObject(); + if (_scarAdapter != null) { + _scarAdapter.show(ClientProperties.getActivity(), queryId, placementId); + } else { + _webViewErrorHandler.handleError(GMAAdsError.InternalShowError(scarAdMetadata, "Scar Adapter object is null")); + } + } + + private IScarAdapter getScarAdapterObject() { + if (_scarAdapter == null) { + long minorVersion = _scarVersionFinder.getGoogleSdkVersionCode(); + _scarAdapter = _scarAdapterFactory.createScarAdapter(minorVersion, _webViewErrorHandler); + } + return _scarAdapter; + } + +} + diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/adapters/ScarAdapterFactory.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/adapters/ScarAdapterFactory.java new file mode 100644 index 00000000..53dad377 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/adapters/ScarAdapterFactory.java @@ -0,0 +1,26 @@ +package com.unity3d.services.ads.gmascar.adapters; + +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarAdapter; +import com.unity3d.services.core.log.DeviceLog; + +public class ScarAdapterFactory { + public static final int CODE_20_0 = 210402000; + public static final int CODE_19_7 = 204204000; + public static final int CODE_19_5 = 203404000; + public static final int CODE_19_2 = 201604000; + + public IScarAdapter createScarAdapter(long gmaVersionCode, IAdsErrorHandler adsErrorHandler) { + IScarAdapter scarAdapter = null; + if (gmaVersionCode >= CODE_20_0) { + scarAdapter = new com.unity3d.scar.adapter.v2000.ScarAdapter(adsErrorHandler); + } else if (gmaVersionCode >= CODE_19_5 && gmaVersionCode <= CODE_19_7) { + scarAdapter = new com.unity3d.scar.adapter.v1950.ScarAdapter(adsErrorHandler); + } else if (gmaVersionCode >= CODE_19_2) { + scarAdapter = new com.unity3d.scar.adapter.v1920.ScarAdapter(adsErrorHandler); + } else { + DeviceLog.debug("SCAR version %s is not supported.", gmaVersionCode); + } + return scarAdapter; + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/AdapterStatusBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/AdapterStatusBridge.java new file mode 100644 index 00000000..835291c6 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/AdapterStatusBridge.java @@ -0,0 +1,43 @@ +package com.unity3d.services.ads.gmascar.bridges; + +import com.unity3d.services.core.log.DeviceLog; +import java.util.HashMap; + +public class AdapterStatusBridge extends GenericBridge { + + private static final String initializeStateMethodName = "getInitializationState"; + + // This class contains the AdapterState Enum + private Class _adapterStateClass; + + public AdapterStatusBridge() { + super(new HashMap() {{ + put(initializeStateMethodName, new Class[]{}); + }}); + AdapterStatusStateBridge adapterStatusStateBridge = new AdapterStatusStateBridge(); + try { + _adapterStateClass = Class.forName(adapterStatusStateBridge.getClassName()); + } catch (ClassNotFoundException e) { + DeviceLog.debug("ERROR: Could not find class %s %s", adapterStatusStateBridge.getClassName(), e.getLocalizedMessage()); + } + } + + protected String getClassName() { + return "com.google.android.gms.ads.initialization.AdapterStatus"; + } + + public boolean isGMAInitialized(Object adapterState) { + Object[] states = getAdapterStatesEnum(); + if (states == null) { + DeviceLog.debug("ERROR: Could not get adapter states enum from AdapterStatus.State"); + return false; + } + + // States[0]: AdapterState.NOT_READY | States[1]: AdapterState.READY + return (callNonVoidMethod(initializeStateMethodName, adapterState, new Object[]{}) == states[1]); + } + + public Object[] getAdapterStatesEnum() { + return _adapterStateClass.getEnumConstants(); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/AdapterStatusStateBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/AdapterStatusStateBridge.java new file mode 100644 index 00000000..f5f275d7 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/AdapterStatusStateBridge.java @@ -0,0 +1,11 @@ +package com.unity3d.services.ads.gmascar.bridges; + +public class AdapterStatusStateBridge { + + public AdapterStatusStateBridge() {}; + + public String getClassName() { + return "com.google.android.gms.ads.initialization.AdapterStatus$State"; + } + +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/GenericBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/GenericBridge.java new file mode 100644 index 00000000..cb74f3ce --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/GenericBridge.java @@ -0,0 +1,126 @@ +package com.unity3d.services.ads.gmascar.bridges; + +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.services.core.log.DeviceLog; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +public abstract class GenericBridge { + + private String _className; + private Map _functionAndParameters; + private Map _methodMap; + private boolean _methodMapBuilt = false; + + protected abstract String getClassName(); + + public GenericBridge(Map functionAndParameters) { + _className = getClassName(); + _functionAndParameters = functionAndParameters; + _methodMap = new HashMap<>(); + buildMethodMap(); + } + + public Map getFunctionMap() { + return _functionAndParameters; + } + + public Class classForName() { + try { + Class getClass = Class.forName(_className); + if (getClass != null) { + return getClass; + } else { + return null; + } + } catch (ClassNotFoundException e) { + DeviceLog.debug("ERROR: Could not find GMA SDK Class %s %s", _className, e.getLocalizedMessage()); + return null; + } + } + + public boolean exists() { + if (classForName() == null) { + DeviceLog.debug("ERROR: Could not find class %s", _className); + return false; + } + + if (!_methodMapBuilt) { + buildMethodMap(); + } + if (_methodMap.size() != getFunctionMap().size()) { + return false; + } + + return true; + } + + private void buildMethodMap() { + boolean methodMapNoErrors = true; + for (Map.Entry entry : getFunctionMap().entrySet()) { + Class[] parameterClasses = entry.getValue(); + try { + Method method = getReflectiveMethod(classForName(), entry.getKey(), parameterClasses); + if (method != null) { + _methodMap.put(entry.getKey(), method); + } + } catch (Exception e) { + DeviceLog.debug("ERROR: Could not find %s class with method %s and parameters : %s", _className, entry.getKey(), parameterClasses); + methodMapNoErrors = false; + } + } + _methodMapBuilt = methodMapNoErrors; + } + + private Method getMethod(String methodName) { + return _methodMap.get(methodName); + } + + private Method getReflectiveMethod(Class methodClass, String methodName, Class... parameterClasses) { + Method method = null; + try { + method = methodClass.getDeclaredMethod(methodName, parameterClasses); + } catch (Exception e) { + DeviceLog.debug("ERROR: Could not find method %s in %s", methodName, methodClass.getName() + + " " + e.getLocalizedMessage()); + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.METHOD_ERROR); + } finally { + return method; + } + } + + public void callVoidMethod(String methodName, Object callingObj, Object... parameters) { + Method method = getMethod(methodName); + if (method == null) { + DeviceLog.debug("ERROR: Could not find method %s", methodName); + return; + } + + try { + method.invoke(callingObj, parameters); + } catch (Exception e) { + DeviceLog.debug("ERROR: Could not invoke method %s : %s", methodName, e.getLocalizedMessage()); + } + } + + public Object callNonVoidMethod(String methodName, Object callingObj, Object... parameters) { + Method method = getMethod(methodName); + if (method == null) { + DeviceLog.debug("ERROR: Could not find method %s", methodName); + return null; + } + + try { + return method.invoke(callingObj, parameters); + } catch (Exception e) { + DeviceLog.debug("ERROR: Could not invoke method %s : %s", methodName, e.getLocalizedMessage()); + } + return null; + + } + +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/InitializationStatusBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/InitializationStatusBridge.java new file mode 100644 index 00000000..ef65c197 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/InitializationStatusBridge.java @@ -0,0 +1,23 @@ +package com.unity3d.services.ads.gmascar.bridges; + +import java.util.HashMap; +import java.util.Map; + +public class InitializationStatusBridge extends GenericBridge { + private static final String adapterStatusMapMethodName = "getAdapterStatusMap"; + + public InitializationStatusBridge() { + super(new HashMap() {{ + put(adapterStatusMapMethodName, new Class[]{}); + }}); + } + + public String getClassName() { + return "com.google.android.gms.ads.initialization.InitializationStatus"; + } + + public Map getAdapterStatusMap(Object initStatusObj) { + return (Map) callNonVoidMethod(adapterStatusMapMethodName, + initStatusObj, new Object[]{}); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/InitializeListenerBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/InitializeListenerBridge.java new file mode 100644 index 00000000..6f753eb7 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/InitializeListenerBridge.java @@ -0,0 +1,55 @@ +package com.unity3d.services.ads.gmascar.bridges; + +import com.unity3d.services.ads.gmascar.listeners.IInitializationStatusListener; +import com.unity3d.services.core.log.DeviceLog; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; + +public class InitializeListenerBridge extends GenericBridge { + private static final String initializationCompleteMethodName = "onInitializationComplete"; + private IInitializationStatusListener _initializationStatusListener; + + public InitializeListenerBridge() { + super(new HashMap(){{ + try { + put(initializationCompleteMethodName, new Class[]{Class.forName("com.google.android.gms.ads.initialization.InitializationStatus")}); + } catch (ClassNotFoundException e) { + DeviceLog.debug("Could not find class \"com.google.android.gms.ads.initialization.InitializationStatus\" %s", e.getLocalizedMessage()); + } + }}); + } + + public String getClassName() { + return "com.google.android.gms.ads.initialization.OnInitializationCompleteListener"; + } + + public void setStatusListener(IInitializationStatusListener initializationStatusListener) { + _initializationStatusListener = initializationStatusListener; + } + + public Object createInitializeListenerProxy() { + try { + Object initProxy = Proxy.newProxyInstance(classForName().getClassLoader(), + new Class[]{classForName()}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + if (method.getName().equals(initializationCompleteMethodName)) { + if (_initializationStatusListener != null) { + // args[0] contains an InitializationStatus object + _initializationStatusListener.onInitializationComplete(args[0]); + } + } + return null; + } + }); + return initProxy; + } catch (Exception e) { + DeviceLog.debug("ERROR: Could not create InitializeCompletionListener"); + } + return null; + } + +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/MobileAdsBridge.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/MobileAdsBridge.java new file mode 100644 index 00000000..199152d6 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/bridges/MobileAdsBridge.java @@ -0,0 +1,46 @@ +package com.unity3d.services.ads.gmascar.bridges; + +import android.content.Context; + +import com.unity3d.services.core.log.DeviceLog; + +import java.util.HashMap; + +public class MobileAdsBridge extends GenericBridge { + private static final String initializeMethodName = "initialize"; + private static final String initializationStatusMethodName = "getInitializationStatus"; + private static final String versionStringMethodName = "getVersionString"; + + public MobileAdsBridge() { + super(new HashMap() {{ + try { + put(initializeMethodName, new Class[]{Context.class, Class.forName("com.google.android.gms.ads.initialization.OnInitializationCompleteListener")}); + } catch (ClassNotFoundException e) { + DeviceLog.debug("Could not find class \"com.google.android.gms.ads.initialization.OnInitializationCompleteListener\" %s", e.getLocalizedMessage()); + } + put(initializationStatusMethodName, new Class[]{}); + put(versionStringMethodName, new Class[]{}); + }}); + } + + public String getClassName() { + return "com.google.android.gms.ads.MobileAds"; + } + + public void initialize(Context context, Object initializeListener) { + callVoidMethod(initializeMethodName, null, new Object[]{context, initializeListener}); + } + + public String getVersionString() { + Object versionString = callNonVoidMethod(versionStringMethodName, null, new Object[]{}); + if (versionString == null) { + return "0.0.0"; + } + return versionString.toString(); + } + + public Object getInitializationStatus () { + return callNonVoidMethod(initializationStatusMethodName, null, new Object[]{}); + } + +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/GMAInitializer.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/GMAInitializer.java new file mode 100644 index 00000000..d30a5edd --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/GMAInitializer.java @@ -0,0 +1,71 @@ +package com.unity3d.services.ads.gmascar.finder; + +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.services.ads.gmascar.bridges.AdapterStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializationStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; +import com.unity3d.services.core.log.DeviceLog; +import com.unity3d.services.core.properties.ClientProperties; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +import java.util.Map; + +public class GMAInitializer { + + private MobileAdsBridge _mobileAdsBridge; + private InitializeListenerBridge _initializationListenerBridge; + private InitializationStatusBridge _initializationStatusBridge; + private AdapterStatusBridge _adapterStatusBridge; + + public GMAInitializer(MobileAdsBridge mobileAdsBridge, InitializeListenerBridge initializeListenerBridge, + InitializationStatusBridge initializationStatusBridge, AdapterStatusBridge adapterStatusBridge) { + _mobileAdsBridge = mobileAdsBridge; + _initializationListenerBridge = initializeListenerBridge; + _initializationStatusBridge = initializationStatusBridge; + _adapterStatusBridge = adapterStatusBridge; + } + + // We need to initialize GMA SDK in order to get the version string + public void initializeGMA() { + if (isInitialized()) { + return; + } else { + _mobileAdsBridge.initialize(ClientProperties.getApplicationContext(), _initializationListenerBridge.createInitializeListenerProxy()); + } + } + + public boolean initSuccessful(Object initStatus) { + Map statusMap = _initializationStatusBridge.getAdapterStatusMap(initStatus); + + Object adapterState = statusMap.get(_mobileAdsBridge.getClassName()); + if (adapterState != null) { + if (_adapterStatusBridge.isGMAInitialized(adapterState)) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.INIT_SUCCESS); + return true; + } else { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.INIT_ERROR); + return false; + } + } + + return false; + } + + public boolean isInitialized() { + boolean isInitialized = false; + try { + isInitialized = initSuccessful(_mobileAdsBridge.getInitializationStatus()); + } catch (Exception e) { + isInitialized = false; + DeviceLog.debug("ERROR: Could not get initialization status of GMA SDK - %s", e.getLocalizedMessage()); + } finally { + return isInitialized; + } + } + + public InitializeListenerBridge getInitializeListenerBridge() { + return _initializationListenerBridge; + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/PresenceDetector.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/PresenceDetector.java new file mode 100644 index 00000000..9b98b966 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/PresenceDetector.java @@ -0,0 +1,31 @@ +package com.unity3d.services.ads.gmascar.finder; + +import com.unity3d.services.ads.gmascar.bridges.AdapterStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializationStatusBridge; +import com.unity3d.services.ads.gmascar.bridges.InitializeListenerBridge; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; + +public class PresenceDetector { + private MobileAdsBridge _mobileAdsBridge; + private InitializeListenerBridge _initializationListenerBridge; + private InitializationStatusBridge _initializationStatusBridge; + private AdapterStatusBridge _adapterStatusBridge; + + public PresenceDetector(MobileAdsBridge mobileAdsBridge, InitializeListenerBridge initializeListenerBridge, + InitializationStatusBridge initializationStatusBridge, AdapterStatusBridge adapterStatusBridge) { + _mobileAdsBridge = mobileAdsBridge; + _initializationListenerBridge = initializeListenerBridge; + _initializationStatusBridge = initializationStatusBridge; + _adapterStatusBridge = adapterStatusBridge; + } + + public boolean areGMAClassesPresent() { + if (_mobileAdsBridge == null || _initializationListenerBridge == null || + _initializationStatusBridge == null || _adapterStatusBridge == null) { + return false; + } + + return _mobileAdsBridge.exists() && _initializationListenerBridge.exists() && _initializationStatusBridge.exists() + && _adapterStatusBridge.exists(); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/ScarVersionFinder.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/ScarVersionFinder.java new file mode 100644 index 00000000..fedbdce1 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/finder/ScarVersionFinder.java @@ -0,0 +1,67 @@ +package com.unity3d.services.ads.gmascar.finder; + +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.services.ads.gmascar.bridges.MobileAdsBridge; +import com.unity3d.services.ads.gmascar.listeners.IInitializationStatusListener; +import com.unity3d.services.core.log.DeviceLog; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +public class ScarVersionFinder implements IInitializationStatusListener { + + private static MobileAdsBridge _mobileAdsBridge; + private PresenceDetector _presenceDetector; + private GMAInitializer _gmaInitializer; + private long _gmaSdkVersionCode = -1; + + public ScarVersionFinder(MobileAdsBridge mobileAdsBridge, PresenceDetector presenceDetector, GMAInitializer gmaInitializer) { + _mobileAdsBridge = mobileAdsBridge; + _presenceDetector = presenceDetector; + _gmaInitializer = gmaInitializer; + _gmaInitializer.getInitializeListenerBridge().setStatusListener(this); + } + + public void getVersion() { + try { + if (!_presenceDetector.areGMAClassesPresent()) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.INIT_GMA, GMAEvent.VERSION, "0.0.0"); + return; + } + + if (!_gmaInitializer.isInitialized()) { + _gmaInitializer.initializeGMA(); + } else { + findAndSendVersion(true); + } + + } catch (Exception e) { + DeviceLog.debug("Got exception finding GMA SDK: %s", e.getLocalizedMessage()); + } + } + + public void findAndSendVersion(boolean initializeSuccess) { + String version = initializeSuccess ? _mobileAdsBridge.getVersionString() : "0.0.0"; + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.INIT_GMA, GMAEvent.VERSION, version); + } + + public long getGoogleSdkVersionCode() { + if (_gmaSdkVersionCode == -1) { + String gmaSdkVersion = _mobileAdsBridge.getVersionString(); + if (gmaSdkVersion != null) { + String[] versionComponents = gmaSdkVersion.split("\\."); + if (versionComponents.length > 1) { + _gmaSdkVersionCode = Long.parseLong(versionComponents[1]); + } + } + } + + return _gmaSdkVersionCode; + } + + @Override + // @param initStatus InitializationStatus Object retrieved through reflection + public void onInitializationComplete(Object initStatus) { + boolean isInitSuccessful = _gmaInitializer.initSuccessful(initStatus); + findAndSendVersion(isInitSuccessful); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/ScarInterstitialAdHandler.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/ScarInterstitialAdHandler.java new file mode 100644 index 00000000..025dedb3 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/ScarInterstitialAdHandler.java @@ -0,0 +1,82 @@ +package com.unity3d.services.ads.gmascar.handlers; + +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +import java.util.Timer; +import java.util.TimerTask; + +public class ScarInterstitialAdHandler implements IScarInterstitialAdListenerWrapper { + + private ScarAdMetadata _scarAdMetadata; + private boolean _finishedPlaying = false; + private boolean _hasSentStartEvents = false; + private Timer _playbackTimer; + private TimerTask _playbackTimerTask = new TimerTask() { + @Override + public void run() { + _finishedPlaying = true; + } + }; + + public ScarInterstitialAdHandler(ScarAdMetadata scarAdMetadata) { + _scarAdMetadata = scarAdMetadata; + _playbackTimer = new Timer(); + } + + @Override + public void onAdLoaded() { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_LOADED, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId()); + } + + @Override + public void onAdFailedToLoad(int errorCode, String errorString) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.LOAD_ERROR, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId(), errorString, errorCode); + } + + @Override + public void onAdOpened() { + // We send all three events back to back since we don't have quartile callbacks from GMA + if (!_hasSentStartEvents) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_STARTED); + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.FIRST_QUARTILE); + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.MIDPOINT); + _hasSentStartEvents = true; + } + // Start timer here to see if the user watched to completion + _finishedPlaying = false; + _playbackTimer.schedule(_playbackTimerTask, _scarAdMetadata.getVideoLengthMs()); + } + + @Override + public void onAdFailedToShow(int errorCode, String errorString) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.INTERSTITIAL_SHOW_ERROR, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId(), errorString, errorCode); + } + + @Override + public void onAdClicked() { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_CLICKED); + } + + @Override + public void onAdClosed() { + if (!_finishedPlaying) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_SKIPPED); + _playbackTimer.cancel(); + } + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_CLOSED); + } + + @Override + public void onAdLeftApplication() { + } + + @Override + public void onAdImpression() { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.INTERSTITIAL_IMPRESSION_RECORDED, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId()); + } + +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/ScarRewardedAdHandler.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/ScarRewardedAdHandler.java new file mode 100644 index 00000000..82033853 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/ScarRewardedAdHandler.java @@ -0,0 +1,85 @@ +package com.unity3d.services.ads.gmascar.handlers; + +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +import java.util.Timer; +import java.util.TimerTask; + +public class ScarRewardedAdHandler implements IScarRewardedAdListenerWrapper { + + private ScarAdMetadata _scarAdMetadata; + private boolean _finishedPlaying = false; + private boolean _hasRewarded = false; + private boolean _hasSentStartEvents = false; + private Timer _playbackTimer; + private TimerTask _playbackTimerTask = new TimerTask() { + @Override + public void run() { + _finishedPlaying = true; + } + }; + + public ScarRewardedAdHandler(ScarAdMetadata scarAdMetadata) { + _scarAdMetadata = scarAdMetadata; + _playbackTimer = new Timer(); + } + + @Override + public void onRewardedAdLoaded() { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_LOADED, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId()); + } + + @Override + public void onRewardedAdFailedToLoad(int errorCode, String errorString) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.LOAD_ERROR, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId(), errorString, errorCode); + } + + @Override + public void onRewardedAdOpened() { + // We send all three events back to back since we don't have quartile callbacks from GMA + if (!_hasSentStartEvents) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_STARTED); + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.FIRST_QUARTILE); + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.MIDPOINT); + _hasSentStartEvents = true; + } + if (!_hasRewarded) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_EARNED_REWARD); + _hasRewarded = true; + } + _finishedPlaying = false; + _playbackTimer.schedule(_playbackTimerTask, _scarAdMetadata.getVideoLengthMs()); + } + + @Override + public void onRewardedAdFailedToShow(int errorCode, String errorString) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.REWARDED_SHOW_ERROR, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId(), errorString, errorCode); + } + + @Override + public void onUserEarnedReward() { + if (!_hasRewarded) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_EARNED_REWARD); + _hasRewarded = true; + } + } + + @Override + public void onRewardedAdClosed() { + if (!_finishedPlaying) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_SKIPPED); + _playbackTimer.cancel(); + } + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.AD_CLOSED); + } + + @Override + public void onAdImpression() { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.REWARDED_IMPRESSION_RECORDED, _scarAdMetadata.getPlacementId(), _scarAdMetadata.getQueryId()); + } + +} \ No newline at end of file diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/SignalsHandler.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/SignalsHandler.java new file mode 100644 index 00000000..24f31b5c --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/SignalsHandler.java @@ -0,0 +1,19 @@ +package com.unity3d.services.ads.gmascar.handlers; + +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +public class SignalsHandler implements ISignalCollectionListener { + + @Override + public void onSignalsCollected(String signalsMap) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.SIGNALS, signalsMap); + } + + @Override + public void onSignalsCollectionFailed(String errorMsg) { + WebViewApp.getCurrentApp().sendEvent(WebViewEventCategory.GMA, GMAEvent.SIGNALS_ERROR, errorMsg); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/WebViewErrorHandler.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/WebViewErrorHandler.java new file mode 100644 index 00000000..f7dcb89e --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/handlers/WebViewErrorHandler.java @@ -0,0 +1,15 @@ +package com.unity3d.services.ads.gmascar.handlers; + +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.WebViewAdsError; +import com.unity3d.services.core.webview.WebViewApp; +import com.unity3d.services.core.webview.WebViewEventCategory; + +public class WebViewErrorHandler implements IAdsErrorHandler { + + @Override + public void handleError(WebViewAdsError webViewAdsError) { + WebViewEventCategory category = WebViewEventCategory.valueOf(webViewAdsError.getDomain()); + WebViewApp.getCurrentApp().sendEvent(category, webViewAdsError.getErrorCategory(), webViewAdsError.getErrorArguments()); + } +} diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/listeners/IInitializationStatusListener.java b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/listeners/IInitializationStatusListener.java new file mode 100644 index 00000000..14020bc6 --- /dev/null +++ b/unity-ads/src/main/java/com/unity3d/services/ads/gmascar/listeners/IInitializationStatusListener.java @@ -0,0 +1,5 @@ +package com.unity3d.services.ads.gmascar.listeners; + +public interface IInitializationStatusListener { + void onInitializationComplete(Object initStatus); +} diff --git a/unity-ads/src/main/java/com/unity3d/services/core/webview/WebViewEventCategory.java b/unity-ads/src/main/java/com/unity3d/services/core/webview/WebViewEventCategory.java index ba7c6d32..27244ce6 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/webview/WebViewEventCategory.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/webview/WebViewEventCategory.java @@ -19,5 +19,7 @@ public enum WebViewEventCategory { PERMISSIONS, STORE, LOAD_API, - TOKEN + TOKEN, + INIT_GMA, + GMA } diff --git a/unity-scaradapter-1920/.gitignore b/unity-scaradapter-1920/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/unity-scaradapter-1920/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/unity-scaradapter-1920/build.gradle b/unity-scaradapter-1920/build.gradle new file mode 100644 index 00000000..86116304 --- /dev/null +++ b/unity-scaradapter-1920/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 30 + multiDexEnabled true + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled false + testCoverageEnabled true + } + } +} + +dependencies { + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation "org.mockito:mockito-android:2.25.0" + androidTestImplementation 'com.google.android.gms:play-services-ads:19.2.0' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:2.28.2' + testImplementation 'com.google.android.gms:play-services-ads:19.2.0' + api project(':unity-scaradapter-common') + compileOnly 'com.google.android.gms:play-services-ads:19.2.0' +} + + +task deleteOldJar(type: Delete) { + delete("../unity-ads/libs/${project.name}.jar") +} + +task copyJars(type: Copy) { + from('build/intermediates/compile_library_classes_jar/release/') + into('../unity-ads/libs/') + include('classes.jar') + rename('classes.jar', "${project.name}.jar") +} + +copyJars.dependsOn(deleteOldJar) + +project.tasks.whenTaskAdded { Task theTask -> + if (theTask.name == 'bundleLibCompileToJarRelease') { + theTask.finalizedBy(copyJars) + } +} diff --git a/unity-scaradapter-1920/consumer-rules.pro b/unity-scaradapter-1920/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/unity-scaradapter-1920/proguard-rules.pro b/unity-scaradapter-1920/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/unity-scaradapter-1920/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/unity-scaradapter-1920/src/androidTest/AndroidManifest.xml b/unity-scaradapter-1920/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..af6b39ba --- /dev/null +++ b/unity-scaradapter-1920/src/androidTest/AndroidManifest.xml @@ -0,0 +1,9 @@ +> + + + + \ No newline at end of file diff --git a/unity-scaradapter-1920/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java b/unity-scaradapter-1920/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java new file mode 100644 index 00000000..b3a759c8 --- /dev/null +++ b/unity-scaradapter-1920/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java @@ -0,0 +1,13 @@ +package com.unity3d.ads.test; + +import com.unity3d.scar.adapter.v1920.signals.SignalsReaderTest; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + SignalsReaderTest.class +}) +public class InstrumentationTestSuite { +} diff --git a/unity-scaradapter-1920/src/androidTest/java/com/unity3d/scar/adapter/v1920/signals/SignalsReaderTest.java b/unity-scaradapter-1920/src/androidTest/java/com/unity3d/scar/adapter/v1920/signals/SignalsReaderTest.java new file mode 100644 index 00000000..7daee56e --- /dev/null +++ b/unity-scaradapter-1920/src/androidTest/java/com/unity3d/scar/adapter/v1920/signals/SignalsReaderTest.java @@ -0,0 +1,48 @@ +package com.unity3d.scar.adapter.v1920.signals; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Matchers.any; + +@RunWith(MockitoJUnitRunner.class) +public class SignalsReaderTest { + private Context context = InstrumentationRegistry.getInstrumentation().getContext(); + private ISignalCollectionListener _signalCollectionListener; + + @Before + public void before() { + _signalCollectionListener = Mockito.mock(ISignalCollectionListener.class); + } + + @Test + public void testGetScarSignals() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{"video"}, new String[]{"rewarded"}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + + @Test + public void testGetScarSignalsNoRewarded() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{"video"}, new String[]{}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + + @Test + public void testGetScarSignalsNoInterstitial() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{}, new String[]{"rewarded"}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + +} diff --git a/unity-scaradapter-1920/src/main/AndroidManifest.xml b/unity-scaradapter-1920/src/main/AndroidManifest.xml new file mode 100644 index 00000000..25f70301 --- /dev/null +++ b/unity-scaradapter-1920/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/ScarAdapter.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/ScarAdapter.java new file mode 100644 index 00000000..ac368883 --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/ScarAdapter.java @@ -0,0 +1,59 @@ +package com.unity3d.scar.adapter.v1920; + +import android.content.Context; + +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarAdapter; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.ScarAdapterBase; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1920.scarads.ScarInterstitialAd; +import com.unity3d.scar.adapter.v1920.scarads.ScarRewardedAd; +import com.unity3d.scar.adapter.v1920.signals.SignalsReader; +import com.unity3d.scar.adapter.v1920.signals.SignalsStorage; + +import static com.unity3d.scar.adapter.common.Utils.runOnUiThread; + +public class ScarAdapter extends ScarAdapterBase implements IScarAdapter { + + private SignalsStorage _scarSignalStorage; + + public ScarAdapter(IAdsErrorHandler adsErrorHandler) { + super(adsErrorHandler); + _scarSignalStorage = new SignalsStorage(); + _scarSignalReader = new SignalsReader(_scarSignalStorage); + } + + public void loadInterstitialAd(Context context, final ScarAdMetadata scarAd, final IScarInterstitialAdListenerWrapper adListenerWrapper) { + final ScarInterstitialAd interstitialAd = new ScarInterstitialAd(context, _scarSignalStorage.getQueryInfoMetadata(scarAd.getPlacementId()), scarAd, _adsErrorHandler, adListenerWrapper); + runOnUiThread(new Runnable() { + @Override + public void run() { + interstitialAd.loadAd(new IScarLoadListener() { + @Override + public void onAdLoaded() { + _loadedAds.put(scarAd.getPlacementId(), interstitialAd); + } + }); + } + }); + } + + public void loadRewardedAd(Context context, final ScarAdMetadata scarAd, final IScarRewardedAdListenerWrapper adListenerWrapper) { + final ScarRewardedAd rewardedAd = new ScarRewardedAd(context, _scarSignalStorage.getQueryInfoMetadata(scarAd.getPlacementId()), scarAd, _adsErrorHandler, adListenerWrapper); + runOnUiThread(new Runnable() { + @Override + public void run() { + rewardedAd.loadAd(new IScarLoadListener() { + @Override + public void onAdLoaded() { + _loadedAds.put(scarAd.getPlacementId(), rewardedAd); + } + }); + } + }); + } + +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarAdBase.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarAdBase.java new file mode 100644 index 00000000..a4d06ee9 --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarAdBase.java @@ -0,0 +1,40 @@ +package com.unity3d.scar.adapter.v1920.scarads; + +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.AdInfo; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.scarads.IScarAd; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1920.signals.QueryInfoMetadata; + +public abstract class ScarAdBase implements IScarAd { + + protected Context _context; + protected ScarAdMetadata _scarAdMetadata; + protected QueryInfoMetadata _queryInfoMetadata; + protected IAdsErrorHandler _adsErrorHandler; + + public ScarAdBase(Context context, ScarAdMetadata scarAdMetadata, QueryInfoMetadata queryInfoMetadata, IAdsErrorHandler adsErrorHandler) { + _context = context; + _scarAdMetadata = scarAdMetadata; + _queryInfoMetadata = queryInfoMetadata; + _adsErrorHandler = adsErrorHandler; + } + + @Override + public void loadAd(IScarLoadListener loadListener) { + if (_queryInfoMetadata != null) { + AdInfo adInfo = new AdInfo(_queryInfoMetadata.getQueryInfo(), _scarAdMetadata.getAdString()); + AdRequest adRequest = new AdRequest.Builder().setAdInfo(adInfo).build(); + loadAdInternal(loadListener, adRequest); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalLoadError(_scarAdMetadata)); + } + } + + protected abstract void loadAdInternal(IScarLoadListener loadListener, AdRequest adRequest); +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarInterstitialAd.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarInterstitialAd.java new file mode 100644 index 00000000..48de52a4 --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarInterstitialAd.java @@ -0,0 +1,42 @@ +package com.unity3d.scar.adapter.v1920.scarads; + +import android.app.Activity; +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.InterstitialAd; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1920.signals.QueryInfoMetadata; + +public class ScarInterstitialAd extends ScarAdBase { + + private InterstitialAd _interstitialAd; + private ScarInterstitialAdListener _interstitialAdDelegate; + + public ScarInterstitialAd(Context context, QueryInfoMetadata queryInfoMetadata, ScarAdMetadata scarAdMetadata, IAdsErrorHandler adsErrorHandler, IScarInterstitialAdListenerWrapper adListener) { + super(context, scarAdMetadata, queryInfoMetadata, adsErrorHandler); + _interstitialAd = new InterstitialAd(_context); + _interstitialAd.setAdUnitId(_scarAdMetadata.getAdUnitId()); + _interstitialAdDelegate = new ScarInterstitialAdListener(_interstitialAd, adListener); + } + + @Override + public void loadAdInternal(IScarLoadListener loadListener, AdRequest adRequest) { + _interstitialAd.setAdListener(_interstitialAdDelegate.getAdListener()); + _interstitialAdDelegate.setLoadListener(loadListener); + _interstitialAd.loadAd(adRequest); + } + + @Override + public void show(Activity activity) { + if (_interstitialAd.isLoaded()) { + _interstitialAd.show(); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalShowError(_scarAdMetadata)); + } + } +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarInterstitialAdListener.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarInterstitialAdListener.java new file mode 100644 index 00000000..568f4b34 --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarInterstitialAdListener.java @@ -0,0 +1,62 @@ +package com.unity3d.scar.adapter.v1920.scarads; + +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.InterstitialAd; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; + +public class ScarInterstitialAdListener { + + private InterstitialAd _interstitialAd; + private IScarInterstitialAdListenerWrapper _adListenerWrapper; + private IScarLoadListener _loadListener; + + public ScarInterstitialAdListener(InterstitialAd interstitialAd, IScarInterstitialAdListenerWrapper adListenerWrapper) { + _interstitialAd = interstitialAd; + _adListenerWrapper = adListenerWrapper; + } + + private AdListener _adListener = new AdListener() { + @Override + public void onAdLoaded() { + _adListenerWrapper.onAdLoaded(); + if (_loadListener != null) { + _loadListener.onAdLoaded(); + } + } + + @Override + public void onAdFailedToLoad(int adErrorCode) { + _adListenerWrapper.onAdFailedToLoad(adErrorCode, "SCAR ad failed to load"); + } + + @Override + public void onAdOpened() { + _adListenerWrapper.onAdOpened(); + } + + @Override + public void onAdClicked() { + _adListenerWrapper.onAdClicked(); + } + + @Override + public void onAdLeftApplication() { + _adListenerWrapper.onAdLeftApplication(); + } + + @Override + public void onAdClosed() { + _adListenerWrapper.onAdClosed(); + } + }; + + public AdListener getAdListener() { + return _adListener; + } + + public void setLoadListener(IScarLoadListener loadListener) { + _loadListener = loadListener; + } + +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarRewardedAd.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarRewardedAd.java new file mode 100644 index 00000000..c261d9ae --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarRewardedAd.java @@ -0,0 +1,41 @@ +package com.unity3d.scar.adapter.v1920.scarads; + +import android.app.Activity; +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.rewarded.RewardedAd; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1920.signals.QueryInfoMetadata; + +public class ScarRewardedAd extends ScarAdBase { + + private RewardedAd _rewardedAd; + private ScarRewardedAdListener _rewardedAdDelegate; + + public ScarRewardedAd(Context context, QueryInfoMetadata queryInfoMetadata, ScarAdMetadata scarAdMetadata, IAdsErrorHandler adsErrorHandler, IScarRewardedAdListenerWrapper adListener) { + super(context, scarAdMetadata, queryInfoMetadata, adsErrorHandler); + _rewardedAd = new RewardedAd(_context, _scarAdMetadata.getAdUnitId()); + _rewardedAdDelegate = new ScarRewardedAdListener(_rewardedAd, adListener); + } + + @Override + public void loadAdInternal(IScarLoadListener loadListener, AdRequest adRequest) { + _rewardedAdDelegate.setLoadListener(loadListener); + _rewardedAd.loadAd(adRequest, _rewardedAdDelegate.getRewardedAdLoadCallback()); + } + + @Override + public void show(Activity activity) { + if (_rewardedAd.isLoaded()) { + _rewardedAd.show(activity, _rewardedAdDelegate.getRewardedAdCallback()); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalShowError(_scarAdMetadata)); + } + } + +} \ No newline at end of file diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarRewardedAdListener.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarRewardedAdListener.java new file mode 100644 index 00000000..4635bf5b --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/scarads/ScarRewardedAdListener.java @@ -0,0 +1,68 @@ +package com.unity3d.scar.adapter.v1920.scarads; + +import com.google.android.gms.ads.rewarded.RewardedAd; + +import com.google.android.gms.ads.rewarded.RewardItem; +import com.google.android.gms.ads.rewarded.RewardedAdCallback; +import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; + +public class ScarRewardedAdListener { + + private RewardedAd _rewardedAd; + private IScarRewardedAdListenerWrapper _adListenerWrapper; + private IScarLoadListener _loadListener; + + public ScarRewardedAdListener(RewardedAd rewardedAd, IScarRewardedAdListenerWrapper adListenerWrapper) { + _rewardedAd = rewardedAd; + _adListenerWrapper = adListenerWrapper; + } + + private RewardedAdLoadCallback _rewardedAdLoadCallback = new RewardedAdLoadCallback() { + @Override + public void onRewardedAdLoaded() { + _adListenerWrapper.onRewardedAdLoaded(); + if (_loadListener != null) { + _loadListener.onAdLoaded(); + } + } + + @Override + public void onRewardedAdFailedToLoad(int adErrorCode) { + _adListenerWrapper.onRewardedAdFailedToLoad(adErrorCode, "SCAR ad failed to show"); + } + }; + + private RewardedAdCallback rewardedAdCallback = new RewardedAdCallback () { + @Override + public void onRewardedAdOpened() { + _adListenerWrapper.onRewardedAdOpened(); + } + + @Override + public void onRewardedAdFailedToShow(int adErrorCode) { + _adListenerWrapper.onRewardedAdFailedToShow(adErrorCode, "SCAR ad failed to show"); + } + + @Override + public void onUserEarnedReward(RewardItem rewardItem) { + _adListenerWrapper.onUserEarnedReward(); + } + + @Override + public void onRewardedAdClosed() { + _adListenerWrapper.onRewardedAdClosed(); + } + }; + + public RewardedAdCallback getRewardedAdCallback() { + return rewardedAdCallback; + } + + public RewardedAdLoadCallback getRewardedAdLoadCallback() { return _rewardedAdLoadCallback; } + + public void setLoadListener(IScarLoadListener loadListener) { + _loadListener = loadListener; + } +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/QueryInfoCallback.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/QueryInfoCallback.java new file mode 100644 index 00000000..cf8ec5b7 --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/QueryInfoCallback.java @@ -0,0 +1,33 @@ +package com.unity3d.scar.adapter.v1920.signals; + +import android.util.Log; + +import com.google.android.gms.ads.query.QueryInfo; +import com.google.android.gms.ads.query.QueryInfoGenerationCallback; +import com.unity3d.scar.adapter.common.DispatchGroup; +import com.unity3d.scar.adapter.v1920.ScarAdapter; + +public class QueryInfoCallback extends QueryInfoGenerationCallback { + + private DispatchGroup _dispatchGroup; + private QueryInfoMetadata _gmaQueryInfoMetadata; + + public QueryInfoCallback(final QueryInfoMetadata gmaQueryInfoMetadata, final DispatchGroup dispatchGroup) { + _dispatchGroup = dispatchGroup; + _gmaQueryInfoMetadata = gmaQueryInfoMetadata; + } + + // Called when QueryInfo generation succeeds + @Override + public void onSuccess(final QueryInfo queryInfo) { + _gmaQueryInfoMetadata.setQueryInfo(queryInfo); + _dispatchGroup.leave(); + } + + // Called when QueryInfo generation fails + @Override + public void onFailure(String failureMsg) { + _gmaQueryInfoMetadata.setError(failureMsg); + _dispatchGroup.leave(); + } +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/QueryInfoMetadata.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/QueryInfoMetadata.java new file mode 100644 index 00000000..08a5eae4 --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/QueryInfoMetadata.java @@ -0,0 +1,41 @@ +package com.unity3d.scar.adapter.v1920.signals; + +import com.google.android.gms.ads.query.QueryInfo; + +public class QueryInfoMetadata { + private String _placementId; + private QueryInfo _queryInfo; + private String _error; + + public QueryInfoMetadata(String placementId) { + _placementId = placementId; + } + + public String getPlacementId() { + return _placementId; + } + + public QueryInfo getQueryInfo() { + return _queryInfo; + } + + public String getQueryStr() { + String query = null; + if (_queryInfo != null) { + query = _queryInfo.getQuery(); + } + return query; + } + + public String getError() { + return _error; + } + + public void setQueryInfo(QueryInfo queryInfo) { + _queryInfo = queryInfo; + } + + public void setError(String error) { + _error = error; + } +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/SignalsReader.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/SignalsReader.java new file mode 100644 index 00000000..801c52ed --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/SignalsReader.java @@ -0,0 +1,85 @@ +package com.unity3d.scar.adapter.v1920.signals; + +import android.content.Context; + +import com.google.android.gms.ads.AdFormat; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.QueryInfo; +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; +import com.unity3d.scar.adapter.common.DispatchGroup; +import com.unity3d.scar.adapter.common.signals.ISignalsReader; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsReader implements ISignalsReader { + private static SignalsStorage _signalsStorage; + + public SignalsReader(SignalsStorage signalsStorage) { + _signalsStorage = signalsStorage; + } + + @Override + public void getSCARSignals(Context context, String[] interstitialList, String[] rewardedList, + ISignalCollectionListener signalCompletionListener) { + DispatchGroup dispatchGroup = new DispatchGroup(); + + for (String interstitialId : interstitialList) { + dispatchGroup.enter(); + getSCARSignal(context, interstitialId, AdFormat.INTERSTITIAL, dispatchGroup); + } + + for (String rewardedId : rewardedList) { + dispatchGroup.enter(); + getSCARSignal(context, rewardedId, AdFormat.REWARDED, dispatchGroup); + } + + dispatchGroup.notify(new GMAScarDispatchCompleted(signalCompletionListener)); + } + + private void getSCARSignal(Context context, String placementId, AdFormat adType, DispatchGroup dispatchGroup) { + AdRequest request = new AdRequest.Builder().build(); + QueryInfoMetadata gmaQueryInfoMetadata = new QueryInfoMetadata(placementId); + QueryInfoCallback gmaQueryInfoCallback = new QueryInfoCallback(gmaQueryInfoMetadata, dispatchGroup); + // Callback on the QueryInfoCallback is async here. + _signalsStorage.put(placementId, gmaQueryInfoMetadata); + QueryInfo.generate(context, adType, request, gmaQueryInfoCallback); + } + + private class GMAScarDispatchCompleted implements Runnable { + + private ISignalCollectionListener _signalListener; + + public GMAScarDispatchCompleted(ISignalCollectionListener signalListener) { + _signalListener = signalListener; + } + + @Override + public void run() { + // Called once every dispatched thread has returned. + // Build up the JSON response since we received all the signals. + Map _placementSignalMap = new HashMap<>(); + String errorMessage = null; + for(Map.Entry queryInfoMetadata : _signalsStorage.getPlacementQueryInfoMap().entrySet()) { + QueryInfoMetadata currentQueryMetadata = queryInfoMetadata.getValue(); + _placementSignalMap.put(currentQueryMetadata.getPlacementId(), currentQueryMetadata.getQueryStr()); + if (currentQueryMetadata.getError() != null) { + // There is an error with one of the signals. + errorMessage = currentQueryMetadata.getError(); + } + } + + if (_placementSignalMap.size() > 0) { + JSONObject placementJSON = new JSONObject(_placementSignalMap); + _signalListener.onSignalsCollected(placementJSON.toString()); + } else if (errorMessage == null){ + _signalListener.onSignalsCollected(""); + } else { + // If no signals could be generated, send SIGNALS_ERROR with the last error message + _signalListener.onSignalsCollectionFailed(errorMessage); + } + } + } +} diff --git a/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/SignalsStorage.java b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/SignalsStorage.java new file mode 100644 index 00000000..e2e9da4d --- /dev/null +++ b/unity-scaradapter-1920/src/main/java/com/unity3d/scar/adapter/v1920/signals/SignalsStorage.java @@ -0,0 +1,21 @@ +package com.unity3d.scar.adapter.v1920.signals; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsStorage { + private Map _placementQueryInfoMap = new HashMap<>(); + + public Map getPlacementQueryInfoMap() { + return _placementQueryInfoMap; + } + + public QueryInfoMetadata getQueryInfoMetadata(String placementId) { + return _placementQueryInfoMap.get(placementId); + } + + public void put(String key, QueryInfoMetadata value) { + _placementQueryInfoMap.put(key, value); + } + +} diff --git a/unity-scaradapter-1920/src/test/java/com/unity3d/scar/adapter/v1920/signals/SignalsStorageTest.java b/unity-scaradapter-1920/src/test/java/com/unity3d/scar/adapter/v1920/signals/SignalsStorageTest.java new file mode 100644 index 00000000..1d91547a --- /dev/null +++ b/unity-scaradapter-1920/src/test/java/com/unity3d/scar/adapter/v1920/signals/SignalsStorageTest.java @@ -0,0 +1,47 @@ +package com.unity3d.scar.adapter.v1920.signals; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsStorageTest { + private static final String TEST_PLACEMENTID = "placementId"; + private static final String TEST_PLACEMENTID2 = "placementId2"; + + private QueryInfoMetadata queryInfoMetadataMock = Mockito.mock(QueryInfoMetadata.class); + + @Test + public void testSignalStorage() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + QueryInfoMetadata queryInfoMetadata = signalsStorage.getQueryInfoMetadata(TEST_PLACEMENTID); + Assert.assertEquals(queryInfoMetadata, queryInfoMetadataMock); + } + + @Test + public void testSignalStorageMultiplePlacements() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + signalsStorage.put(TEST_PLACEMENTID2, queryInfoMetadataMock); + Map expectedStorageContent = new HashMap() { + { + put(TEST_PLACEMENTID, queryInfoMetadataMock); + put(TEST_PLACEMENTID2, queryInfoMetadataMock); + } + }; + Map queryInfoMetadataMap = signalsStorage.getPlacementQueryInfoMap(); + Assert.assertEquals(expectedStorageContent, queryInfoMetadataMap); + } + + @Test + public void testSignalStorageDuplicatePlacements() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + QueryInfoMetadata queryInfoMetadata = signalsStorage.getQueryInfoMetadata(TEST_PLACEMENTID); + Assert.assertEquals(queryInfoMetadata, queryInfoMetadataMock); + } +} diff --git a/unity-scaradapter-1950/.gitignore b/unity-scaradapter-1950/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/unity-scaradapter-1950/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/unity-scaradapter-1950/build.gradle b/unity-scaradapter-1950/build.gradle new file mode 100644 index 00000000..03c5ff1b --- /dev/null +++ b/unity-scaradapter-1950/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 30 + multiDexEnabled true + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled false + testCoverageEnabled true + } + } +} + +dependencies { + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation "org.mockito:mockito-android:2.25.0" + androidTestImplementation 'com.google.android.gms:play-services-ads:19.5.0' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:2.28.2' + testImplementation 'com.google.android.gms:play-services-ads:19.2.0' + api project(':unity-scaradapter-common') + compileOnly 'com.google.android.gms:play-services-ads:19.5.0' +} + +task deleteOldJar(type: Delete) { + delete("../unity-ads/libs/${project.name}.jar") +} + +task copyJars(type: Copy) { + from('build/intermediates/compile_library_classes_jar/release/') + into('../unity-ads/libs/') + include('classes.jar') + rename('classes.jar', "${project.name}.jar") +} + +copyJars.dependsOn(deleteOldJar) + +project.tasks.whenTaskAdded { Task theTask -> + if (theTask.name == 'bundleLibCompileToJarRelease') { + theTask.finalizedBy(copyJars) + } +} \ No newline at end of file diff --git a/unity-scaradapter-1950/consumer-rules.pro b/unity-scaradapter-1950/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/unity-scaradapter-1950/proguard-rules.pro b/unity-scaradapter-1950/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/unity-scaradapter-1950/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/unity-scaradapter-1950/src/androidTest/AndroidManifest.xml b/unity-scaradapter-1950/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..4d4bd469 --- /dev/null +++ b/unity-scaradapter-1950/src/androidTest/AndroidManifest.xml @@ -0,0 +1,8 @@ +> + + + + \ No newline at end of file diff --git a/unity-scaradapter-1950/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java b/unity-scaradapter-1950/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java new file mode 100644 index 00000000..c71c7a39 --- /dev/null +++ b/unity-scaradapter-1950/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java @@ -0,0 +1,13 @@ +package com.unity3d.ads.test; + +import com.unity3d.scar.adapter.v1950.signals.SignalsReaderTest; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + SignalsReaderTest.class +}) +public class InstrumentationTestSuite { +} diff --git a/unity-scaradapter-1950/src/androidTest/java/com/unity3d/scar/adapter/v1950/signals/SignalsReaderTest.java b/unity-scaradapter-1950/src/androidTest/java/com/unity3d/scar/adapter/v1950/signals/SignalsReaderTest.java new file mode 100644 index 00000000..a373dcaa --- /dev/null +++ b/unity-scaradapter-1950/src/androidTest/java/com/unity3d/scar/adapter/v1950/signals/SignalsReaderTest.java @@ -0,0 +1,48 @@ +package com.unity3d.scar.adapter.v1950.signals; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Matchers.any; + +@RunWith(MockitoJUnitRunner.class) +public class SignalsReaderTest { + private Context context = InstrumentationRegistry.getInstrumentation().getContext(); + private ISignalCollectionListener _signalCollectionListener; + + @Before + public void before() { + _signalCollectionListener = Mockito.mock(ISignalCollectionListener.class); + } + + @Test + public void testGetScarSignals() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{"video"}, new String[]{"rewarded"}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + + @Test + public void testGetScarSignalsNoRewarded() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{"video"}, new String[]{}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + + @Test + public void testGetScarSignalsNoInterstitial() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{}, new String[]{"rewarded"}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + +} diff --git a/unity-scaradapter-1950/src/main/AndroidManifest.xml b/unity-scaradapter-1950/src/main/AndroidManifest.xml new file mode 100644 index 00000000..726faf58 --- /dev/null +++ b/unity-scaradapter-1950/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/ScarAdapter.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/ScarAdapter.java new file mode 100644 index 00000000..04507679 --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/ScarAdapter.java @@ -0,0 +1,59 @@ +package com.unity3d.scar.adapter.v1950; + +import android.content.Context; + +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarAdapter; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.ScarAdapterBase; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1950.scarads.ScarInterstitialAd; +import com.unity3d.scar.adapter.v1950.scarads.ScarRewardedAd; +import com.unity3d.scar.adapter.v1950.signals.SignalsReader; +import com.unity3d.scar.adapter.v1950.signals.SignalsStorage; + +import static com.unity3d.scar.adapter.common.Utils.runOnUiThread; + +public class ScarAdapter extends ScarAdapterBase implements IScarAdapter { + + private SignalsStorage _scarSignalStorage; + + public ScarAdapter(IAdsErrorHandler adsErrorHandler) { + super(adsErrorHandler); + _scarSignalStorage = new SignalsStorage(); + _scarSignalReader = new SignalsReader(_scarSignalStorage); + } + + public void loadInterstitialAd(Context context, final ScarAdMetadata scarAd, final IScarInterstitialAdListenerWrapper adListenerWrapper) { + final ScarInterstitialAd interstitialAd = new ScarInterstitialAd(context, _scarSignalStorage.getQueryInfoMetadata(scarAd.getPlacementId()), scarAd, _adsErrorHandler, adListenerWrapper); + runOnUiThread(new Runnable() { + @Override + public void run() { + interstitialAd.loadAd(new IScarLoadListener() { + @Override + public void onAdLoaded() { + _loadedAds.put(scarAd.getPlacementId(), interstitialAd); + } + }); + } + }); + } + + public void loadRewardedAd(Context context, final ScarAdMetadata scarAd, final IScarRewardedAdListenerWrapper adListenerWrapper) { + final ScarRewardedAd rewardedAd = new ScarRewardedAd(context, _scarSignalStorage.getQueryInfoMetadata(scarAd.getPlacementId()), scarAd, _adsErrorHandler, adListenerWrapper); + runOnUiThread(new Runnable() { + @Override + public void run() { + rewardedAd.loadAd(new IScarLoadListener() { + @Override + public void onAdLoaded() { + _loadedAds.put(scarAd.getPlacementId(), rewardedAd); + } + }); + } + }); + } + +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarAdBase.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarAdBase.java new file mode 100644 index 00000000..ec5819db --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarAdBase.java @@ -0,0 +1,41 @@ +package com.unity3d.scar.adapter.v1950.scarads; + +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.AdInfo; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.scarads.IScarAd; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1950.signals.QueryInfoMetadata; + +public abstract class ScarAdBase implements IScarAd { + + protected Context _context; + protected ScarAdMetadata _scarAdMetadata; + protected QueryInfoMetadata _queryInfoMetadata; + protected IAdsErrorHandler _adsErrorHandler; + + public ScarAdBase(Context context, ScarAdMetadata scarAdMetadata, QueryInfoMetadata queryInfoMetadata, IAdsErrorHandler adsErrorHandler) { + _context = context; + _scarAdMetadata = scarAdMetadata; + _queryInfoMetadata = queryInfoMetadata; + _adsErrorHandler = adsErrorHandler; + } + + @Override + public void loadAd(IScarLoadListener loadListener) { + if (_queryInfoMetadata != null) { + AdInfo adInfo = new AdInfo(_queryInfoMetadata.getQueryInfo(), _scarAdMetadata.getAdString()); + AdRequest adRequest = new AdRequest.Builder().setAdInfo(adInfo).build(); + loadAdInternal(loadListener, adRequest); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalLoadError(_scarAdMetadata)); + } + } + + protected abstract void loadAdInternal(IScarLoadListener loadListener, AdRequest adRequest); + +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarInterstitialAd.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarInterstitialAd.java new file mode 100644 index 00000000..27ac17be --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarInterstitialAd.java @@ -0,0 +1,42 @@ +package com.unity3d.scar.adapter.v1950.scarads; + +import android.app.Activity; +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.InterstitialAd; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1950.signals.QueryInfoMetadata; + +public class ScarInterstitialAd extends ScarAdBase { + + private InterstitialAd _interstitialAd; + private ScarInterstitialAdListener _interstitialAdDelegate; + + public ScarInterstitialAd(Context context, QueryInfoMetadata queryInfoMetadata, ScarAdMetadata scarAdMetadata, IAdsErrorHandler adsErrorHandler, IScarInterstitialAdListenerWrapper adListener) { + super(context, scarAdMetadata, queryInfoMetadata, adsErrorHandler); + _interstitialAd = new InterstitialAd(_context); + _interstitialAd.setAdUnitId(_scarAdMetadata.getAdUnitId()); + _interstitialAdDelegate = new ScarInterstitialAdListener(_interstitialAd, adListener); + } + + @Override + public void loadAdInternal(IScarLoadListener loadListener, AdRequest adRequest) { + _interstitialAd.setAdListener(_interstitialAdDelegate.getAdListener()); + _interstitialAdDelegate.setLoadListener(loadListener); + _interstitialAd.loadAd(adRequest); + } + + @Override + public void show(Activity activity) { + if (_interstitialAd.isLoaded()) { + _interstitialAd.show(); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalShowError(_scarAdMetadata)); + } + } +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarInterstitialAdListener.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarInterstitialAdListener.java new file mode 100644 index 00000000..12c47e17 --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarInterstitialAdListener.java @@ -0,0 +1,63 @@ +package com.unity3d.scar.adapter.v1950.scarads; + +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.InterstitialAd; +import com.google.android.gms.ads.LoadAdError; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; + +public class ScarInterstitialAdListener { + + private InterstitialAd _interstitialAd; + private IScarInterstitialAdListenerWrapper _adListenerWrapper; + private IScarLoadListener _loadListener; + + public ScarInterstitialAdListener(InterstitialAd interstitialAd, IScarInterstitialAdListenerWrapper adListenerWrapper) { + _interstitialAd = interstitialAd; + _adListenerWrapper = adListenerWrapper; + } + + private AdListener _adListener = new AdListener() { + @Override + public void onAdLoaded() { + _adListenerWrapper.onAdLoaded(); + if (_loadListener != null) { + _loadListener.onAdLoaded(); + } + } + + @Override + public void onAdFailedToLoad(LoadAdError adErrorCode) { + _adListenerWrapper.onAdFailedToLoad(adErrorCode.getCode(), adErrorCode.toString()); + } + + @Override + public void onAdOpened() { + _adListenerWrapper.onAdOpened(); + } + + @Override + public void onAdClicked() { + _adListenerWrapper.onAdClicked(); + } + + @Override + public void onAdLeftApplication() { + _adListenerWrapper.onAdLeftApplication(); + } + + @Override + public void onAdClosed() { + _adListenerWrapper.onAdClosed(); + } + }; + + public AdListener getAdListener() { + return _adListener; + } + + public void setLoadListener(IScarLoadListener loadListener) { + _loadListener = loadListener; + } + +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarRewardedAd.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarRewardedAd.java new file mode 100644 index 00000000..4d7d4864 --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarRewardedAd.java @@ -0,0 +1,41 @@ +package com.unity3d.scar.adapter.v1950.scarads; + +import android.app.Activity; +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.rewarded.RewardedAd; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v1950.signals.QueryInfoMetadata; + +public class ScarRewardedAd extends ScarAdBase { + + private RewardedAd _rewardedAd; + private ScarRewardedAdListener _rewardedAdDelegate; + + public ScarRewardedAd(Context context, QueryInfoMetadata queryInfoMetadata, ScarAdMetadata scarAdMetadata, IAdsErrorHandler adsErrorHandler, IScarRewardedAdListenerWrapper adListener) { + super(context, scarAdMetadata, queryInfoMetadata, adsErrorHandler); + _rewardedAd = new RewardedAd(_context, _scarAdMetadata.getAdUnitId()); + _rewardedAdDelegate = new ScarRewardedAdListener(_rewardedAd, adListener); + } + + @Override + public void loadAdInternal(IScarLoadListener loadListener, AdRequest adRequest) { + _rewardedAdDelegate.setLoadListener(loadListener); + _rewardedAd.loadAd(adRequest, _rewardedAdDelegate.getRewardedAdLoadCallback()); + } + + @Override + public void show(Activity activity) { + if (_rewardedAd.isLoaded()) { + _rewardedAd.show(activity, _rewardedAdDelegate.getRewardedAdCallback()); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalShowError(_scarAdMetadata)); + } + } + +} \ No newline at end of file diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarRewardedAdListener.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarRewardedAdListener.java new file mode 100644 index 00000000..3b9e93af --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/scarads/ScarRewardedAdListener.java @@ -0,0 +1,69 @@ +package com.unity3d.scar.adapter.v1950.scarads; + +import com.google.android.gms.ads.AdError; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.rewarded.RewardItem; +import com.google.android.gms.ads.rewarded.RewardedAd; +import com.google.android.gms.ads.rewarded.RewardedAdCallback; +import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; + +public class ScarRewardedAdListener { + + private RewardedAd _rewardedAd; + private IScarRewardedAdListenerWrapper _adListenerWrapper; + private IScarLoadListener _loadListener; + + public ScarRewardedAdListener(RewardedAd rewardedAd, IScarRewardedAdListenerWrapper adListenerWrapper) { + _rewardedAd = rewardedAd; + _adListenerWrapper = adListenerWrapper; + } + + private RewardedAdLoadCallback _rewardedAdLoadCallback = new RewardedAdLoadCallback() { + @Override + public void onRewardedAdLoaded() { + _adListenerWrapper.onRewardedAdLoaded(); + if (_loadListener != null) { + _loadListener.onAdLoaded(); + } + } + + @Override + public void onRewardedAdFailedToLoad(LoadAdError adError) { + _adListenerWrapper.onRewardedAdFailedToLoad(adError.getCode(), adError.toString()); + } + }; + + private RewardedAdCallback rewardedAdCallback = new RewardedAdCallback () { + @Override + public void onRewardedAdOpened() { + _adListenerWrapper.onRewardedAdOpened(); + } + + @Override + public void onRewardedAdFailedToShow(AdError adError) { + _adListenerWrapper.onRewardedAdFailedToShow(adError.getCode(), adError.toString()); + } + + @Override + public void onUserEarnedReward(RewardItem rewardItem) { + _adListenerWrapper.onUserEarnedReward(); + } + + @Override + public void onRewardedAdClosed() { + _adListenerWrapper.onRewardedAdClosed(); + } + }; + + public RewardedAdCallback getRewardedAdCallback() { + return rewardedAdCallback; + } + + public RewardedAdLoadCallback getRewardedAdLoadCallback() { return _rewardedAdLoadCallback; } + + public void setLoadListener(IScarLoadListener loadListener) { + _loadListener = loadListener; + } +} \ No newline at end of file diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/QueryInfoCallback.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/QueryInfoCallback.java new file mode 100644 index 00000000..08abaa00 --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/QueryInfoCallback.java @@ -0,0 +1,33 @@ +package com.unity3d.scar.adapter.v1950.signals; + +import android.util.Log; + +import com.google.android.gms.ads.query.QueryInfo; +import com.google.android.gms.ads.query.QueryInfoGenerationCallback; +import com.unity3d.scar.adapter.common.DispatchGroup; +import com.unity3d.scar.adapter.v1950.ScarAdapter; + +public class QueryInfoCallback extends QueryInfoGenerationCallback { + + private DispatchGroup _dispatchGroup; + private QueryInfoMetadata _gmaQueryInfoMetadata; + + public QueryInfoCallback(final QueryInfoMetadata gmaQueryInfoMetadata, final DispatchGroup dispatchGroup) { + _dispatchGroup = dispatchGroup; + _gmaQueryInfoMetadata = gmaQueryInfoMetadata; + } + + // Called when QueryInfo generation succeeds + @Override + public void onSuccess(final QueryInfo queryInfo) { + _gmaQueryInfoMetadata.setQueryInfo(queryInfo); + _dispatchGroup.leave(); + } + + // Called when QueryInfo generation fails + @Override + public void onFailure(String failureMsg) { + _gmaQueryInfoMetadata.setError(failureMsg); + _dispatchGroup.leave(); + } +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/QueryInfoMetadata.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/QueryInfoMetadata.java new file mode 100644 index 00000000..adc8103a --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/QueryInfoMetadata.java @@ -0,0 +1,41 @@ +package com.unity3d.scar.adapter.v1950.signals; + +import com.google.android.gms.ads.query.QueryInfo; + +public class QueryInfoMetadata { + private String _placementId; + private QueryInfo _queryInfo; + private String _error; + + public QueryInfoMetadata(String placementId) { + _placementId = placementId; + } + + public String getPlacementId() { + return _placementId; + } + + public QueryInfo getQueryInfo() { + return _queryInfo; + } + + public String getQueryStr() { + String query = null; + if (_queryInfo != null) { + query = _queryInfo.getQuery(); + } + return query; + } + + public String getError() { + return _error; + } + + public void setQueryInfo(QueryInfo queryInfo) { + _queryInfo = queryInfo; + } + + public void setError(String error) { + _error = error; + } +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/SignalsReader.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/SignalsReader.java new file mode 100644 index 00000000..726cca89 --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/SignalsReader.java @@ -0,0 +1,84 @@ +package com.unity3d.scar.adapter.v1950.signals; + +import android.content.Context; + +import com.google.android.gms.ads.AdFormat; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.QueryInfo; +import com.unity3d.scar.adapter.common.DispatchGroup; +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; +import com.unity3d.scar.adapter.common.signals.ISignalsReader; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsReader implements ISignalsReader { + private static SignalsStorage _signalsStorage; + + public SignalsReader(SignalsStorage signalsStorage) { + _signalsStorage = signalsStorage; + } + + public void getSCARSignals(Context context, String[] interstitialList, String[] rewardedList, + ISignalCollectionListener signalCompletionListener) { + DispatchGroup dispatchGroup = new DispatchGroup(); + + for (String interstitialId : interstitialList) { + dispatchGroup.enter(); + getSCARSignal(context, interstitialId, AdFormat.INTERSTITIAL, dispatchGroup); + } + + for (String rewardedId : rewardedList) { + dispatchGroup.enter(); + getSCARSignal(context, rewardedId, AdFormat.REWARDED, dispatchGroup); + } + + dispatchGroup.notify(new GMAScarDispatchCompleted(signalCompletionListener)); + } + + private void getSCARSignal(Context context, String placementId, AdFormat adType, DispatchGroup dispatchGroup) { + AdRequest request = new AdRequest.Builder().build(); + QueryInfoMetadata gmaQueryInfoMetadata = new QueryInfoMetadata(placementId); + QueryInfoCallback gmaQueryInfoCallback = new QueryInfoCallback(gmaQueryInfoMetadata, dispatchGroup); + // Callback on the QueryInfoCallback is async here. + _signalsStorage.put(placementId, gmaQueryInfoMetadata); + QueryInfo.generate(context, adType, request, gmaQueryInfoCallback); + } + + private class GMAScarDispatchCompleted implements Runnable { + + private ISignalCollectionListener _signalListener; + + public GMAScarDispatchCompleted(ISignalCollectionListener signalListener) { + _signalListener = signalListener; + } + + @Override + public void run() { + // Called once every dispatched thread has returned. + // Build up the JSON response since we received all the signals. + Map _placementSignalMap = new HashMap<>(); + String errorMessage = null; + for(Map.Entry queryInfoMetadata : _signalsStorage.getPlacementQueryInfoMap().entrySet()) { + QueryInfoMetadata currentQueryMetadata = queryInfoMetadata.getValue(); + _placementSignalMap.put(currentQueryMetadata.getPlacementId(), currentQueryMetadata.getQueryStr()); + if (currentQueryMetadata.getError() != null) { + // There is an error with one of the signals. + errorMessage = currentQueryMetadata.getError(); + } + } + + if (_placementSignalMap.size() > 0) { + JSONObject placementJSON = new JSONObject(_placementSignalMap); + _signalListener.onSignalsCollected(placementJSON.toString()); + } else if (errorMessage == null){ + _signalListener.onSignalsCollected(""); + } else { + // If no signals could be generated, send SIGNALS_ERROR with the last error message + _signalListener.onSignalsCollectionFailed(errorMessage); + } + } + } +} diff --git a/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/SignalsStorage.java b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/SignalsStorage.java new file mode 100644 index 00000000..a0eedf4b --- /dev/null +++ b/unity-scaradapter-1950/src/main/java/com/unity3d/scar/adapter/v1950/signals/SignalsStorage.java @@ -0,0 +1,21 @@ +package com.unity3d.scar.adapter.v1950.signals; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsStorage { + private Map _placementQueryInfoMap = new HashMap<>(); + + public Map getPlacementQueryInfoMap() { + return _placementQueryInfoMap; + } + + public QueryInfoMetadata getQueryInfoMetadata(String placementId) { + return _placementQueryInfoMap.get(placementId); + } + + public void put(String key, QueryInfoMetadata value) { + _placementQueryInfoMap.put(key, value); + } + +} diff --git a/unity-scaradapter-1950/src/test/java/com/unity3d/scar/adapter/v1950/signals/SignalsStorageTest.java b/unity-scaradapter-1950/src/test/java/com/unity3d/scar/adapter/v1950/signals/SignalsStorageTest.java new file mode 100644 index 00000000..98188855 --- /dev/null +++ b/unity-scaradapter-1950/src/test/java/com/unity3d/scar/adapter/v1950/signals/SignalsStorageTest.java @@ -0,0 +1,49 @@ +package com.unity3d.scar.adapter.v1950.signals; + +import android.app.DownloadManager; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsStorageTest { + private static final String TEST_PLACEMENTID = "placementId"; + private static final String TEST_PLACEMENTID2 = "placementId2"; + + private QueryInfoMetadata queryInfoMetadataMock = Mockito.mock(QueryInfoMetadata.class); + + @Test + public void testSignalStorage() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + QueryInfoMetadata queryInfoMetadata = signalsStorage.getQueryInfoMetadata(TEST_PLACEMENTID); + Assert.assertEquals(queryInfoMetadata, queryInfoMetadataMock); + } + + @Test + public void testSignalStorageMultiplePlacements() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + signalsStorage.put(TEST_PLACEMENTID2, queryInfoMetadataMock); + Map expectedStorageContent = new HashMap() { + { + put(TEST_PLACEMENTID, queryInfoMetadataMock); + put(TEST_PLACEMENTID2, queryInfoMetadataMock); + } + }; + Map queryInfoMetadataMap = signalsStorage.getPlacementQueryInfoMap(); + Assert.assertEquals(expectedStorageContent, queryInfoMetadataMap); + } + + @Test + public void testSignalStorageDuplicatePlacements() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + signalsStorage.put(TEST_PLACEMENTID, queryInfoMetadataMock); + QueryInfoMetadata queryInfoMetadata = signalsStorage.getQueryInfoMetadata(TEST_PLACEMENTID); + Assert.assertEquals(queryInfoMetadata, queryInfoMetadataMock); + } +} diff --git a/unity-scaradapter-2000/.gitignore b/unity-scaradapter-2000/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/unity-scaradapter-2000/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/unity-scaradapter-2000/build.gradle b/unity-scaradapter-2000/build.gradle new file mode 100644 index 00000000..a1f69fe1 --- /dev/null +++ b/unity-scaradapter-2000/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 30 + multiDexEnabled true + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled false + testCoverageEnabled true + } + } +} + +dependencies { + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation "org.mockito:mockito-android:2.25.0" + androidTestImplementation 'com.google.android.gms:play-services-ads:20.1.0' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:2.28.2' + testImplementation 'com.google.android.gms:play-services-ads:20.1.0' + api project(':unity-scaradapter-common') + compileOnly 'com.google.android.gms:play-services-ads:20.1.0' +} + +task deleteOldJar(type: Delete) { + delete("../unity-ads/libs/${project.name}.jar") +} + +task copyJars(type: Copy) { + from('build/intermediates/compile_library_classes_jar/release/') + into('../unity-ads/libs/') + include('classes.jar') + rename('classes.jar', "${project.name}.jar") +} + +copyJars.dependsOn(deleteOldJar) + +project.tasks.whenTaskAdded { Task theTask -> + if (theTask.name == 'bundleLibCompileToJarRelease') { + theTask.finalizedBy(copyJars) + } +} \ No newline at end of file diff --git a/unity-scaradapter-2000/proguard-rules.pro b/unity-scaradapter-2000/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/unity-scaradapter-2000/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/unity-scaradapter-2000/src/androidTest/AndroidManifest.xml b/unity-scaradapter-2000/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..6b531d94 --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/AndroidManifest.xml @@ -0,0 +1,9 @@ +> + + + + + \ No newline at end of file diff --git a/unity-scaradapter-2000/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java new file mode 100644 index 00000000..4aa718fa --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/ads/test/InstrumentationTestSuite.java @@ -0,0 +1,13 @@ +package com.unity3d.ads.test; + +import com.unity3d.scar.adapter.v2000.signals.SignalsReaderTest; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + SignalsReaderTest.class +}) +public class InstrumentationTestSuite { +} diff --git a/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/Constants.java b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/Constants.java new file mode 100644 index 00000000..065526a2 --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/Constants.java @@ -0,0 +1,6 @@ +package com.unity3d.scar.adapter.v2000; + +class Constants { + public static String SCARExampleAdUnitId = "752589458"; + public static String SCARExampleInterstitialAdString = "{\"ad_type\":\"\",\"qdata\":\"x\",\"ad_networks\":[{\"ad\":{\"ad_base_url\":\"https://0.shared.bow.bid.ads-bow.dg.borg.google.com\",\"ad_html\":\"\\u003c!DOCTYPE html\\u003e\\u003chtml\\u003e\\u003chead\\u003e\\u003cscript\\u003e(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','https://www.google-analytics.com/analytics.js','gao');\\u003c/script\\u003e\\u003cscript\\u003efunction a(b){this.t={};this.tick=function(b,f,d){this.t[b]=[void 0!=d?d:(new Date).getTime(),f]};this.tick(\\\"start\\\",null,b)}var c=new a;window.jstiming={Timer:a,load:c};try{var e=null;null==e\\u0026\\u0026window.gtbExternal\\u0026\\u0026(e=window.gtbExternal.pageT());null==e\\u0026\\u0026window.external\\u0026\\u0026(e=window.external.pageT);e\\u0026\\u0026(window.jstiming.pt=e)}catch(g){}var h=window.onerror;window.onerror=function(){window.jstiming.jsError=1;\\\"function\\\"==typeof h\\u0026\\u0026h.apply(window,arguments)};\\u003c/script\\u003e\\u003cmeta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"/\\u003e\\u003cmeta name=\\\"viewport\\\"content=\\\"width=device-width,height=device-height,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0\\\"/\\u003e\\u003clink rel=\\\"stylesheet\\\" href=\\\"https://imasdk.googleapis.com/admob/sdkloader/video_ctd_campari.css\\\"\\u003e\\u003cscript src=\\\"https://imasdk.googleapis.com/admob/sdkloader/video_ctd_campari.js\\\"\\u003e\\u003c/script\\u003e\\u003cscript src=\\\"https://tpc.googlesyndication.com/pagead/gadgets/html5/layout_engine.js\\\"\\u003e\\u003c/script\\u003e\\u003c/head\\u003e\\u003cbody\\u003e\\u003cscript\\u003evar html5AdData = {google_width: 414,google_height: 896,google_click_url: 'https://googleads.g.doubleclick.net/aclk?sa\\\\x3dL\\\\x26ai\\\\x3dC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\\\x26ae\\\\x3d1\\\\x26num\\\\x3d1\\\\x26cid\\\\x3dCAASUeRoXbfm-muHmdVBigpult8aj8xgkIVSz0WxTJURc_UCNQZhZEi8J-m3ue7XRpZJftpK7rBRblzlxFqJ9BVnofHBAODJRko7oidb9N9SA4AWvg\\\\x26sig\\\\x3dAOD64_12K2xdOpcUJsRpv0Da0OP49oOi7g\\\\x26adurl\\\\x3d',google_ait_url: '',redirect_url: 'https://googleads.g.doubleclick.net/aclk?sa\\\\x3dL\\\\x26ai\\\\x3dC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\\\x26ae\\\\x3d1\\\\x26num\\\\x3d1\\\\x26cid\\\\x3dCAASUeRoXbfm-muHmdVBigpult8aj8xgkIVSz0WxTJURc_UCNQZhZEi8J-m3ue7XRpZJftpK7rBRblzlxFqJ9BVnofHBAODJRko7oidb9N9SA4AWvg\\\\x26sig\\\\x3dAOD64_12K2xdOpcUJsRpv0Da0OP49oOi7g\\\\x26adurl\\\\x3dhttps://itunes.apple.com/app/id1176011642%3Fmt%3D8%26gclid%3DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE',visible_url: '',destination_url: 'https://itunes.apple.com/app/id1176011642?mt\\\\x3d8',final_url: 'https://itunes.apple.com/app/id1176011642?mt\\\\x3d8',evc_touch: 'true',link_target: '_top',google_template_data: {rendering_settings: {'format': 'interstitial_mb','octagonSdkRequest': 'true','screenDensity': '2','bgSignal': 'true','enableRtl': 'true'},'adData': [{'appId': '1176011642','appStore': '1','headline': 'Featured by App Store.','description': 'The most addictive game.','video': '','layout': 'video_ctd_campari','afma_orientation_header': 'portrait','is_video_replaced_by_mbf': 'false','is_video_replaced_by_preview': 'false','afma_sdk_version': 'afma-sdk-i-v7.51.0','is_unity': 'false','delay_page_loaded_timeout': '0','delay_page_loaded_timeout_action': 'start_ad','use_afma_video_player': 'true','afma_video_player_type': '2','enable_preloading_in_ima': 'true','afma_precache_min_buffer': '5000','load_video_timeout': '3000','load_video_timeout_to_enable_close': '0','client_side_flags': 'use_grey_back\\\\x3dv2;delay_screenshots\\\\x3dtrue;screenshots_limit\\\\x3d0;optimizeScreenshots\\\\x3dtrue;minimal_click_delay\\\\x3d1000;start_timer_after_onshow\\\\x3d-1;not_pause_countdown\\\\x3dtrue;exp_set_afma_header\\\\x3dtrue;optimize_screenshot_v2\\\\x3dtrue;use_baby_ima\\\\x3dtrue;cinderella_click_delay\\\\x3d2000;alice_click_delay\\\\x3d2000;use_new_close_button\\\\x3dtrue;use_new_translucent_close_button\\\\x3dtrue;force_using_fallback_start_image_in_china\\\\x3dtrue;use_fall_back_color_theme\\\\x3dtrue;use_native_button_color\\\\x3dtrue;replace_old_appstore_badge\\\\x3dtrue;install_button_as_only_clickable_element\\\\x3dtrue;show_unified_video_ui\\\\x3dtrue;use_playback_control_widget\\\\x3dtrue;ios_inline_install\\\\x3dtrue;use_campari_rect_banner_style\\\\x3dtrue;use_short_app_name_in_rect_banner\\\\x3dtrue;handle_volume_afma_events\\\\x3dtrue;','app_store_overlay_capable': 'true','animated_inline_install_capable': 'false','audio_state': 'AUDIO_VIBRATE','is_iphonex_safemode': 'false','safe_rendering_without_hybrid_wrapper': 'true','safe_margin': '40px 0px 40px 0px','safe_width': '414','safe_height': '816','csi_experiment_ids': '318483348,318485135,318483745,','ima_experiment_ids': '21062273,21063062,420706116','network_type': 'wi','gws_query_id': '4E-3XdaEC7OCtgezhL74Bg','query_id': 'CNnuu_Tlv-UCFcX47QodTJIFWQ','campaign_id': '1641246191','creative_id': '354095021505','web_property': 'ca-mb-app-pub-4487379466152215','adslot_id': '','format': 'interstitial_mb','creative_conversion_url_without_label': 'https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ','is_fhp_creative': 'false','redirect_url': 'https://googleads.g.doubleclick.net/aclk?sa\\\\x3dL\\\\x26ai\\\\x3dC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\\\x26ae\\\\x3d1\\\\x26num\\\\x3d1\\\\x26cid\\\\x3dCAASUeRoXbfm-muHmdVBigpult8aj8xgkIVSz0WxTJURc_UCNQZhZEi8J-m3ue7XRpZJftpK7rBRblzlxFqJ9BVnofHBAODJRko7oidb9N9SA4AWvg\\\\x26sig\\\\x3dAOD64_12K2xdOpcUJsRpv0Da0OP49oOi7g\\\\x26adurl\\\\x3dhttps://itunes.apple.com/app/id1176011642%3Fmt%3D8%26gclid%3DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE','destination_url': 'https://itunes.apple.com/app/id1176011642?mt\\\\x3d8','u2_final_url': 'https://itunes.apple.com/app/id1176011642?mt\\\\x3d8','u2_tracking_url': 'https://itunes.apple.com/app/id1176011642?mt\\\\x3d8','deep_link_url': '','color_theme': 'NONE','vast_xml': '\\\\x3c?xml version\\\\x3d\\\\x221.0\\\\x22 encoding\\\\x3d\\\\x22UTF-8\\\\x22?\\\\x3e\\\\n\\\\x3cVAST xmlns:xsi\\\\x3d\\\\x22http://www.w3.org/2001/XMLSchema-instance\\\\x22 xsi:noNamespaceSchemaLocation\\\\x3d\\\\x22vast.xsd\\\\x22 version\\\\x3d\\\\x223.0\\\\x22\\\\x3e\\\\n \\\\x3cAd id\\\\x3d\\\\x22316916631062\\\\x22\\\\x3e\\\\n \\\\x3cInLine\\\\x3e\\\\n \\\\x3cAdSystem\\\\x3eAdSense\\\\x3c/AdSystem\\\\x3e\\\\n \\\\x3cAdTitle\\\\x3evideo\\\\x3c/AdTitle\\\\x3e\\\\n \\\\x3cError\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dvideoplayfailed[ERRORCODE]]]\\\\x3e\\\\x3c/Error\\\\x3e\\\\n \\\\x3cCreatives\\\\x3e\\\\n \\\\x3cCreative sequence\\\\x3d\\\\x221\\\\x22\\\\x3e\\\\n \\\\x3cLinear\\\\x3e\\\\n \\\\x3cDuration\\\\x3e00:00:30\\\\x3c/Duration\\\\x3e\\\\n \\\\x3cTrackingEvents\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22start\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dpart2viewed]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22firstQuartile\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dvideoplaytime25]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22midpoint\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dvideoplaytime50]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22thirdQuartile\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dvideoplaytime75]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22complete\\\\x22\\\\x3e\\\\x3c![CDATA[https://pagead2.googleadservices.com/pagead/adview?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dxm3-cLv2uRo\\\\x26cid\\\\x3dCAQSWgDwy9IZiMbB4y5-_VohWM5rkfUKpjx1lXYjisehoQmA2_AjPg25rEU82XtVB-1s3L6cfbuwnZfMoHluY7KXQDEXUcRlMDOfD3sCGRYQZ2W7TfPoeWnqtJQ2Xg\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26gvr\\\\x3d1\\\\x26vt\\\\x3d9\\\\x26ad_mt\\\\x3d[AD_MT]\\\\x26label\\\\x3dvideoplaytime100]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22complete\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dvideoplaytime100]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22pause\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dadpause]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3cTracking event\\\\x3d\\\\x22resume\\\\x22\\\\x3e\\\\x3c![CDATA[https://googleads.g.doubleclick.net/pagead/conversion/?ai\\\\x3dChwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\\\x26sigh\\\\x3dPGsAu-tzATA\\\\x26cmd\\\\x3dCh5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\\\x26label\\\\x3dadresume]]\\\\x3e\\\\x3c/Tracking\\\\x3e\\\\n \\\\x3c/TrackingEvents\\\\x3e\\\\n \\\\x3cMediaFiles\\\\x3e\\\\n \\\\x3cMediaFile delivery\\\\x3d\\\\x22progressive\\\\x22 width\\\\x3d\\\\x22300\\\\x22 height\\\\x3d\\\\x22400\\\\x22 type\\\\x3d\\\\x22video/mp4\\\\x22 bitrate\\\\x3d\\\\x22320\\\\x22 scalable\\\\x3d\\\\x22true\\\\x22 maintainAspectRatio\\\\x3d\\\\x22true\\\\x22\\\\x3e\\\\x3c![CDATA[https://faulty.media]]\\\\x3e\\\\x3c/MediaFile\\\\x3e\\\\n \\\\x3c/MediaFiles\\\\x3e\\\\n \\\\x3c/Linear\\\\x3e\\\\n \\\\x3c/Creative\\\\x3e\\\\n \\\\x3c/Creatives\\\\x3e\\\\n \\\\x3cExtensions/\\\\x3e\\\\n \\\\x3c/InLine\\\\x3e\\\\n \\\\x3c/Ad\\\\x3e\\\\n\\\\x3c/VAST\\\\x3e\\\\n','video_url': 'gcache://video/media/-3883059050846329675?url\\\\x3dhttps://www.youtube.com/get_video%3Fvideo_id%3DyhyaXdhwZLU%26ts%3D1572294624%26t%3DnbbPOasW9UAmZViteN4iqIL5NTc%26gad%3D1%26br%3D1%26itag%3D22,18\\\\x26tag.duration\\\\x3d30000\\\\x26tag.check_url\\\\x3dhttps://www.youtube.com/get_video%3Fvideo_id%3DyhyaXdhwZLU%26ts%3D1572294624%26t%3DnbbPOasW9UAmZViteN4iqIL5NTc%26gad%3D1%26br%3D1%26itag%3D22,18','media_id': '-3883059050846329675','videoAspectRatio': '0.56300002336502075','video_format': '22','is_youtube_hosted': 'true','video_id': 'yhyaXdhwZLU','click_to_play': 'false','mute_at_start': 'false','video_duration': '30000','skippable_seconds': '5','app_id': '1176011642','app_name': 'Galaxy Attack: Alien Shooter','short_app_name': 'Galaxy Attack','icon_image_url': 'https://lh3.googleusercontent.com/pllZEagDaMDzPqhoOZC3tFXKmHDe-T4UmHlUCJaENJ8YG1EKcHL0CN1mWnRsjXa-tzYbHlRTRcQ\\\\x3dl80','developer_name': 'ABIGAMES PTE. LTD','price': 'FREE','rating_num': '4.53','localized_review_number_bucket': '200 thousand','rating_image': 'https://googleads.g.doubleclick.net/pagead/images/gmob/4_5-stars-orange-material.svg','review_count': '296838','app_icon_color_0': '#0d0812','app_icon_color_1': '#1a0d29','star_icon': 'https://googleads.g.doubleclick.net/pagead/images/gmob/star_full_white_24dp.svg','app_store_logo_image_url': 'https://googleads.g.doubleclick.net/pagead/images/gmob/iphone/badge/Apple_Store_Badge_EN.svg','category': '60506','screenshot_0_image_url': 'https://lh3.googleusercontent.com/7p43T6nFYlhoSWQiwPUCdpXDUqU732WyoKXINqT8oKqc5COg-5pE5NwEywIu2i14FLl1Ne22\\\\x3dl80-rj','screenshot_0_image_height': '696','screenshot_0_image_width': '392','screenshot_1_image_url': 'https://lh3.googleusercontent.com/aGSln2mgl-v1AaUMO-LglsXph93NM1MjmjH7CKXLQedTtgbmA0FliQBJWqzbDl6w3qDVyzxFaqk\\\\x3dl80-rj','screenshot_1_image_height': '696','screenshot_1_image_width': '392','screenshot_2_image_url': 'https://lh3.googleusercontent.com/odod4-1prM4YiWT8IfRozuj2UIFd_pagut-wJ4R_Ty4tz8mewh7kY5sEKdfl5nM8OonItoY4\\\\x3dl80-rj','screenshot_2_image_height': '696','screenshot_2_image_width': '392','screenshot_3_image_url': 'https://lh3.googleusercontent.com/J9_4LDRmUlayYFK9VKggTQItEfmOlxuTpQfm01XdtdAybNFIM6mziPbaFB-Pb_zm1Yp0PEOO\\\\x3dl80-rj','screenshot_3_image_height': '696','screenshot_3_image_width': '392','is_svg_supported': 'true','review_text': 'Reviews','rating_text': '%1$s Ratings','country': 'ME','language': 'en','is_pre_registration': 'false','button_text': 'GET','pub_app_id': '','pub_icon': '','pub_name': '','continue_with_app_name': 'Continue to %1$s in %2$d','continue_without_app_name': 'Continue in %1$d','continue_with_app_name_no_countdown': 'Continue to %1$s','continue_without_app_name_no_countdown': 'Continue','siriusFlagBackgroundUnclickable': 'true','gpaSpecifiedLogo': 'true','gpaAddPromoText': 'true','hot_item_label': 'Hot','gpaFlagBackgroundUnclickable': 'true','FLAG_pass_gclid_to_deeplink_url': 'True','FLAG_client_side_flag_overrides': '[{\\\\x22name\\\\x22 : \\\\x22uses_octagon_sdk\\\\x22, \\\\x22value\\\\x22 : true},{\\\\x22name\\\\x22 : \\\\x22open_applinks_adding_gclid\\\\x22, \\\\x22value\\\\x22 : true}]','gpaUseWideLogo': 'true','gpaAddNewItem': 'mock','gpaLimitAnimationTime': 'first','versionInfo': '7.3.2','gpaPriceDropType': 'animation','gpaFlagBgSignalClickLocationEnabled': 'true'}]}};if (window.onAdData) {window.onAdData(html5AdData);}\\u003c/script\\u003e\\u003cstyle\\u003ediv{margin:0;padding:0;}.abgcp{height:15px;padding-right:1px;padding-bottom:1px;padding-left:9px;padding-top:24px;right:0px;bottom:0px;position:absolute;width:15px;z-index:2147483646;}.abgc{display:block;height:15px;position:absolute;right:1px;bottom:1px;text-rendering:geometricPrecision;z-index:2147483646;}.abgb{display:inline-block;height:15px;}.abgc,.abgcp,.jar .abgc,.jar .abgcp,.jar .cbb{opacity:1;}.abgs{display:none;height:100%;}.abgl{text-decoration:none;}.abgs svg,.abgb svg{display:inline-block;height:15px;width:auto;vertical-align:top;}.abgc .il-wrap{background-color:#ffffff;height:15px;white-space:nowrap;}.abgc .il-wrap.exp{border-top-left-radius:5px;}.abgc .il-text,.abgc .il-icon{display:inline-block;}.abgc .il-text{padding-right:1px;padding-left:5px;height:15px;width:74px;}.abgc .il-icon{height:15px;width:15px;}.abgc .il-text svg{fill:#000000;}.abgc .il-icon svg{fill:#00aecd}\\u003c/style\\u003e\\u003cdiv id=\\\"abgcp\\\" class=\\\"abgcp\\\"\\u003e\\u003cdiv id=\\\"abgc\\\" class=\\\"abgc\\\" dir=\\\"ltr\\\" aria-hidden=\\\"true\\\"\\u003e\\u003cdiv id=\\\"abgb\\\" class=\\\"abgb\\\"\\u003e\\u003cdiv class=\\\"il-wrap\\\"\\u003e\\u003cdiv class=\\\"il-icon\\\"\\u003e\\u003csvg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" viewBox=\\\"0 0 15 15\\\"\\u003e\\u003cpath d=\\\"M7.5,1.5a6,6,0,1,0,0,12a6,6,0,1,0,0,-12m0,1a5,5,0,1,1,0,10a5,5,0,1,1,0,-10ZM6.625,11l1.75,0l0,-4.5l-1.75,0ZM7.5,3.75a1,1,0,1,0,0,2a1,1,0,1,0,0,-2Z\\\"\\u003e\\u003c/path\\u003e\\u003c/svg\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003cdiv id=\\\"abgs\\\" class=\\\"abgs\\\"\\u003e\\u003ca id=\\\"abgl\\\" class=\\\"abgl\\\" href=\\\"https://adssettings.google.com/whythisad?reasons=AB3afGEAAAFXW1tbW10sW251bGwsbnVsbCxbbnVsbCxudWxsLG51bGwsImh0dHBzOi8vZGlzcGxheWFkcy1mb3JtYXRzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hZHMvcHJldmlldy9jb250ZW50LmpzP2NsaWVudD13dGFcdTAwMjZvYmZ1c2NhdGVkQ3VzdG9tZXJJZD01NTYxMzM5NDM3XHUwMDI2Y3JlYXRpdmVJZD0zNTQwOTUwMjE1MDVcdTAwMjZ2ZXJzaW9uSWQ9MVx1MDAyNmFkR3JvdXBDcmVhdGl2ZUlkPTMxNjkxNjYzMTA2Mlx1MDAyNmh0bWxQYXJlbnRJZD1wcmV2LTFcdTAwMjZoZWlnaHQ9MFx1MDAyNndpZHRoPTBcdTAwMjZzaWc9QUNpVkJfeGVHemhFNDJiYWtRQ0dRbWRuNXZxU3lmbUNsZyJdXV1dLFsxLDJdXRR-61Yi-an6KhZi4ofQTX0H9y6cQkqBTu_y5qzXAuiZ8dqZWqf-OGrlhMYMqlkTsDP9B9gvk_H01VcSr-U4uqRp1ujVSY_3JH72yf_YpnZKPR-udMfp09jBZyJpLk_TTRZGZ0LlGnjKC7w5gTyNN5BgRLsMes5mw33A8cP9n066avQF_PMDU4riHT9AoNeTIDCm5KsbNsx_3noJ6fqrVavx75DXN43wAdT6fBCrOyBBm0ZfNJ5E7JBlrfKXVT-cL2VkkHj5jIjHaXj_xBIgzTwdtyKuWzDgN0KGf640uXNjNGHxKY__6Up_czrdEqcqs0l11faurOnHZ_YTiVt-Sq0,m_2FlvsgJUokxDd5UqPLkg\\u0026amp;source=display\\u0026amp;cbt=HR2j8IIKFT4IlozQzZwJEO_bzY4GGJXpv2lSCjExNzYwMTE2NDJwAQ\\u0026amp;cv=https://googleads.g.doubleclick.net/pagead/conversion/%3Fai%3DC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ%26sigh%3DW6w-9snWChI%26cid%3DCAQSWgDwy9IZiMbB4y5-_VohWM5rkfUKpjx1lXYjisehoQmA2_AjPg25rEU82XtVB-1s3L6cfbuwnZfMoHluY7KXQDEXUcRlMDOfD3sCGRYQZ2W7TfPoeWnqtJQ2Xg\\\" target=\\\"_blank\\\"\\u003e\\u003cdiv class=\\\"il-wrap exp\\\"\\u003e\\u003cdiv class=\\\"il-text\\\"\\u003e\\u003csvg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" viewBox=\\\"0 0 79 16\\\"\\u003e\\u003cpath d=\\\"M4.51 4.24L8.02 12.83L6.73 12.83L5.72 10.21L2.14 10.21L1.19 12.83L-0.01 12.83L3.29 4.24L4.51 4.24ZM2.46 9.31L5.38 9.31L5.38 9.31Q4.32 6.52 4.19 6.14L4.19 6.14L4.19 6.14Q4.05 5.77 3.89 5.13L3.89 5.13L3.86 5.13L3.86 5.13Q3.70 6 3.42 6.74L3.42 6.74L2.46 9.31ZM13.83 4.24L13.83 12.83L12.85 12.83L12.85 12.04L12.83 12.04L12.83 12.04Q12.26 12.97 11.11 12.97L11.11 12.97L11.11 12.97Q9.97 12.97 9.20 12.07L9.20 12.07L9.20 12.07Q8.44 11.16 8.44 9.72L8.44 9.72L8.44 9.72Q8.44 8.21 9.16 7.34L9.16 7.34L9.16 7.34Q9.88 6.47 11.06 6.47L11.06 6.47L11.06 6.47Q12.18 6.47 12.76 7.32L12.76 7.32L12.78 7.32L12.78 4.24L13.83 4.24ZM11.22 12.11L11.22 12.11L11.22 12.11Q11.91 12.11 12.39 11.54L12.39 11.54L12.39 11.54Q12.87 10.97 12.87 9.81L12.87 9.81L12.87 9.81Q12.87 8.68 12.43 8.00L12.43 8.00L12.43 8.00Q11.99 7.33 11.17 7.33L11.17 7.33L11.17 7.33Q10.35 7.33 9.93 7.99L9.93 7.99L9.93 7.99Q9.52 8.65 9.52 9.72L9.52 9.72L9.52 9.72Q9.52 10.49 9.74 11.03L9.74 11.03L9.74 11.03Q9.97 11.57 10.37 11.84L10.37 11.84L10.37 11.84Q10.78 12.11 11.22 12.11ZM19.98 8.21L18.95 8.35L18.95 8.35Q18.88 7.84 18.54 7.58L18.54 7.58L18.54 7.58Q18.19 7.33 17.58 7.33L17.58 7.33L17.58 7.33Q16.98 7.33 16.61 7.54L16.61 7.54L16.61 7.54Q16.24 7.74 16.24 8.13L16.24 8.13L16.24 8.13Q16.24 8.50 16.53 8.66L16.53 8.66L16.53 8.66Q16.83 8.82 17.70 9.05L17.70 9.05L17.70 9.05Q18.69 9.30 19.17 9.49L19.17 9.49L19.17 9.49Q19.66 9.69 19.94 10.03L19.94 10.03L19.94 10.03Q20.21 10.38 20.21 11.00L20.21 11.00L20.21 11.00Q20.21 11.84 19.53 12.40L19.53 12.40L19.53 12.40Q18.85 12.97 17.71 12.97L17.71 12.97L17.71 12.97Q16.53 12.97 15.86 12.47L15.86 12.47L15.86 12.47Q15.20 11.97 15.05 10.97L15.05 10.97L16.10 10.81L16.10 10.81Q16.18 11.46 16.58 11.78L16.58 11.78L16.58 11.78Q16.98 12.11 17.70 12.11L17.70 12.11L17.70 12.11Q18.39 12.11 18.76 11.82L18.76 11.82L18.76 11.82Q19.13 11.53 19.13 11.11L19.13 11.11L19.13 11.11Q19.13 10.83 18.96 10.66L18.96 10.66L18.96 10.66Q18.79 10.49 18.54 10.40L18.54 10.40L18.54 10.40Q18.28 10.32 17.39 10.10L17.39 10.10L17.39 10.10Q16.07 9.79 15.64 9.33L15.64 9.33L15.64 9.33Q15.22 8.88 15.22 8.26L15.22 8.26L15.22 8.26Q15.22 7.48 15.84 6.97L15.84 6.97L15.84 6.97Q16.46 6.47 17.51 6.47L17.51 6.47L17.51 6.47Q18.62 6.47 19.24 6.91L19.24 6.91L19.24 6.91Q19.86 7.34 19.98 8.21L19.98 8.21ZM25.87 7.31L25.90 7.31L25.90 7.31Q26.48 6.47 27.57 6.47L27.57 6.47L27.57 6.47Q28.78 6.47 29.50 7.34L29.50 7.34L29.50 7.34Q30.22 8.21 30.22 9.62L30.22 9.62L30.22 9.62Q30.22 11.24 29.41 12.11L29.41 12.11L29.41 12.11Q28.61 12.97 27.53 12.97L27.53 12.97L27.53 12.97Q26.43 12.97 25.82 12.05L25.82 12.05L25.80 12.05L25.80 12.83L24.81 12.83L24.81 4.24L25.87 4.24L25.87 7.31ZM27.45 12.11L27.45 12.11L27.45 12.11Q28.18 12.11 28.66 11.48L28.66 11.48L28.66 11.48Q29.13 10.85 29.13 9.71L29.13 9.71L29.13 9.71Q29.13 8.61 28.69 7.97L28.69 7.97L28.69 7.97Q28.25 7.33 27.48 7.33L27.48 7.33L27.48 7.33Q26.72 7.33 26.25 7.99L26.25 7.99L26.25 7.99Q25.79 8.65 25.79 9.67L25.79 9.67L25.79 9.67Q25.79 10.66 25.98 11.08L25.98 11.08L25.98 11.08Q26.17 11.50 26.56 11.80L26.56 11.80L26.56 11.80Q26.95 12.11 27.45 12.11ZM35.52 6.61L36.59 6.61L34.21 12.93L34.21 12.93Q34.21 12.94 34.20 12.97L34.20 12.97L34.20 12.97Q33.86 14.03 33.41 14.69L33.41 14.69L33.41 14.69Q32.95 15.35 32.12 15.35L32.12 15.35L32.12 15.35Q31.83 15.35 31.43 15.23L31.43 15.23L31.32 14.23L31.32 14.23Q31.63 14.33 31.92 14.33L31.92 14.33L31.92 14.33Q32.51 14.33 32.73 14.00L32.73 14.00L32.73 14.00Q32.96 13.68 33.24 12.84L33.24 12.84L30.88 6.61L32.02 6.61L33.48 10.68L33.48 10.68Q33.61 11.06 33.76 11.65L33.76 11.65L33.78 11.65L33.78 11.65Q33.93 10.97 34.15 10.35L34.15 10.35L35.52 6.61ZM48.59 8.44L48.59 11.63L48.59 11.63Q46.96 12.97 45.04 12.97L45.04 12.97L45.04 12.97Q43.06 12.97 41.85 11.77L41.85 11.77L41.85 11.77Q40.64 10.56 40.64 8.57L40.64 8.57L40.64 8.57Q40.64 7.35 41.14 6.30L41.14 6.30L41.14 6.30Q41.63 5.24 42.59 4.67L42.59 4.67L42.59 4.67Q43.56 4.09 44.95 4.09L44.95 4.09L44.95 4.09Q46.40 4.09 47.28 4.73L47.28 4.73L47.28 4.73Q48.15 5.36 48.46 6.63L48.46 6.63L47.44 6.92L47.44 6.92Q47.20 5.99 46.58 5.53L46.58 5.53L46.58 5.53Q45.96 5.06 44.95 5.06L44.95 5.06L44.95 5.06Q43.40 5.06 42.61 6.01L42.61 6.01L42.61 6.01Q41.82 6.96 41.82 8.50L41.82 8.50L41.82 8.50Q41.82 9.64 42.20 10.41L42.20 10.41L42.20 10.41Q42.59 11.17 43.34 11.56L43.34 11.56L43.34 11.56Q44.09 11.95 44.98 11.95L44.98 11.95L44.98 11.95Q46.32 11.95 47.48 11.06L47.48 11.06L47.48 9.47L44.95 9.47L44.95 8.44L48.59 8.44ZM52.69 6.47L52.69 6.47L52.69 6.47Q53.95 6.47 54.78 7.29L54.78 7.29L54.78 7.29Q55.61 8.11 55.61 9.62L55.61 9.62L55.61 9.62Q55.61 11.46 54.71 12.21L54.71 12.21L54.71 12.21Q53.81 12.97 52.69 12.97L52.69 12.97L52.69 12.97Q51.51 12.97 50.64 12.19L50.64 12.19L50.64 12.19Q49.78 11.41 49.78 9.72L49.78 9.72L49.78 9.72Q49.78 8.08 50.61 7.27L50.61 7.27L50.61 7.27Q51.45 6.47 52.69 6.47ZM52.69 12.11L52.69 12.11L52.69 12.11Q53.59 12.11 54.06 11.44L54.06 11.44L54.06 11.44Q54.53 10.77 54.53 9.68L54.53 9.68L54.53 9.68Q54.53 8.51 54.00 7.92L54.00 7.92L54.00 7.92Q53.47 7.33 52.69 7.33L52.69 7.33L52.69 7.33Q51.88 7.33 51.37 7.93L51.37 7.93L51.37 7.93Q50.85 8.53 50.85 9.72L50.85 9.72L50.85 9.72Q50.85 10.90 51.38 11.50L51.38 11.50L51.38 11.50Q51.90 12.11 52.69 12.11ZM59.36 6.47L59.36 6.47L59.36 6.47Q60.62 6.47 61.45 7.29L61.45 7.29L61.45 7.29Q62.28 8.11 62.28 9.62L62.28 9.62L62.28 9.62Q62.28 11.46 61.38 12.21L61.38 12.21L61.38 12.21Q60.49 12.97 59.36 12.97L59.36 12.97L59.36 12.97Q58.18 12.97 57.32 12.19L57.32 12.19L57.32 12.19Q56.45 11.41 56.45 9.72L56.45 9.72L56.45 9.72Q56.45 8.08 57.29 7.27L57.29 7.27L57.29 7.27Q58.13 6.47 59.36 6.47ZM59.36 12.11L59.36 12.11L59.36 12.11Q60.26 12.11 60.73 11.44L60.73 11.44L60.73 11.44Q61.20 10.77 61.20 9.68L61.20 9.68L61.20 9.68Q61.20 8.51 60.67 7.92L60.67 7.92L60.67 7.92Q60.14 7.33 59.36 7.33L59.36 7.33L59.36 7.33Q58.56 7.33 58.04 7.93L58.04 7.93L58.04 7.93Q57.53 8.53 57.53 9.72L57.53 9.72L57.53 9.72Q57.53 10.90 58.05 11.50L58.05 11.50L58.05 11.50Q58.58 12.11 59.36 12.11ZM68.60 6.61L68.60 11.99L68.60 11.99Q68.60 13.31 68.33 13.97L68.33 13.97L68.33 13.97Q68.07 14.62 67.39 14.98L67.39 14.98L67.39 14.98Q66.72 15.35 65.78 15.35L65.78 15.35L65.78 15.35Q64.73 15.35 64.03 14.88L64.03 14.88L64.03 14.88Q63.32 14.41 63.32 13.34L63.32 13.34L64.35 13.50L64.35 13.50Q64.41 13.99 64.76 14.24L64.76 14.24L64.76 14.24Q65.12 14.48 65.77 14.48L65.77 14.48L65.77 14.48Q66.59 14.48 66.97 14.15L66.97 14.15L66.97 14.15Q67.35 13.82 67.44 13.34L67.44 13.34L67.44 13.34Q67.52 12.86 67.52 12.01L67.52 12.01L67.52 12.01Q66.84 12.83 65.79 12.83L65.79 12.83L65.79 12.83Q64.58 12.83 63.85 11.93L63.85 11.93L63.85 11.93Q63.11 11.03 63.11 9.67L63.11 9.67L63.11 9.67Q63.11 8.30 63.83 7.38L63.83 7.38L63.83 7.38Q64.54 6.47 65.81 6.47L65.81 6.47L65.81 6.47Q66.91 6.47 67.60 7.35L67.60 7.35L67.62 7.35L67.62 6.61L68.60 6.61ZM65.89 11.96L65.89 11.96L65.89 11.96Q66.57 11.96 67.09 11.43L67.09 11.43L67.09 11.43Q67.61 10.89 67.61 9.63L67.61 9.63L67.61 9.63Q67.61 8.52 67.11 7.92L67.11 7.92L67.11 7.92Q66.62 7.33 65.88 7.33L65.88 7.33L65.88 7.33Q65.13 7.33 64.66 7.94L64.66 7.94L64.66 7.94Q64.19 8.55 64.19 9.61L64.19 9.61L64.19 9.61Q64.19 10.83 64.68 11.40L64.68 11.40L64.68 11.40Q65.18 11.96 65.89 11.96ZM71.23 4.24L71.23 12.83L70.18 12.83L70.18 4.24L71.23 4.24ZM78.25 9.98L78.25 9.98L73.59 9.98L73.59 9.98Q73.65 11.02 74.18 11.56L74.18 11.56L74.18 11.56Q74.70 12.11 75.49 12.11L75.49 12.11L75.49 12.11Q76.09 12.11 76.50 11.79L76.50 11.79L76.50 11.79Q76.91 11.47 77.13 10.82L77.13 10.82L78.21 10.96L78.21 10.96Q77.95 11.93 77.24 12.45L77.24 12.45L77.24 12.45Q76.54 12.97 75.49 12.97L75.49 12.97L75.49 12.97Q74.09 12.97 73.30 12.11L73.30 12.11L73.30 12.11Q72.51 11.26 72.51 9.77L72.51 9.77L72.51 9.77Q72.51 8.30 73.27 7.38L73.27 7.38L73.27 7.38Q74.04 6.47 75.43 6.47L75.43 6.47L75.43 6.47Q76.11 6.47 76.74 6.77L76.74 6.77L76.74 6.77Q77.37 7.07 77.81 7.78L77.81 7.78L77.81 7.78Q78.25 8.50 78.25 9.98ZM73.65 9.12L77.16 9.12L77.16 9.12Q77.10 8.17 76.57 7.75L76.57 7.75L76.57 7.75Q76.05 7.33 75.43 7.33L75.43 7.33L75.43 7.33Q74.68 7.33 74.20 7.83L74.20 7.83L74.20 7.83Q73.72 8.33 73.65 9.12L73.65 9.12Z\\\"/\\u003e\\u003c/svg\\u003e\\u003c/div\\u003e\\u003cdiv class=\\\"il-icon\\\"\\u003e\\u003csvg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" viewBox=\\\"0 0 15 15\\\"\\u003e\\u003cpath d=\\\"M7.5,1.5a6,6,0,1,0,0,12a6,6,0,1,0,0,-12m0,1a5,5,0,1,1,0,10a5,5,0,1,1,0,-10ZM6.625,11l1.75,0l0,-4.5l-1.75,0ZM7.5,3.75a1,1,0,1,0,0,2a1,1,0,1,0,0,-2Z\\\"\\u003e\\u003c/path\\u003e\\u003c/svg\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/a\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003cscript src=\\\"https://tpc.googlesyndication.com/pagead/js/r20191024/r20110914/abg_lite.js\\\" data-jc=\\\"60\\\"\\u003e\\u003c/script\\u003e\\u003cscript\\u003ebuildAttribution([[null,\\\"https://googleads.g.doubleclick.net/pagead/images/mtad/x_white.png\\\",null,\\\"https://googleads.g.doubleclick.net/pagead/images/mtad/x_white.png\\\",\\\"https://googleads.g.doubleclick.net/pagead/conversion/?ai=C8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\\\u0026sigh=W6w-9snWChI\\\\u0026cid=CAQSWgDwy9IZiMbB4y5-_VohWM5rkfUKpjx1lXYjisehoQmA2_AjPg25rEU82XtVB-1s3L6cfbuwnZfMoHluY7KXQDEXUcRlMDOfD3sCGRYQZ2W7TfPoeWnqtJQ2Xg\\\",\\\"HR2j8IIKFT4IlozQzZwJEO_bzY4GGJXpv2lSCjExNzYwMTE2NDJwAQ\\\",[\\\"user_feedback_menu_interaction\\\",\\\"\\\",0],null,null,null,null,\\\"What was wrong with this ad?\\\",null,\\\"https://googleads.g.doubleclick.net/pagead/images/mtad/back_blue.png\\\",\\\"Thanks for the feedback!\\\",\\\"We’ll review this ad to improve your experience in the future.\\\",\\\"Thanks for the feedback!\\\",\\\"We’ll use your feedback to review ads on this site.\\\",null,null,null,\\\"Closing ad: %1$d\\\",\\\"Ads by Google\\\",\\\"https://googleads.g.doubleclick.net/pagead/images/mtad/abg_blue.png\\\",\\\"https://www.google.com/ads/preferences/html/mobile-about.html\\\",\\\"https://googleads.g.doubleclick.net/pagead/images/mtad/x_white.png\\\",0,[[\\\"Stop seeing this ad\\\",[\\\"user_feedback_menu_option\\\",\\\"1\\\",1],[\\\"What was wrong with this ad?\\\",[[\\\"Not interested in this ad\\\",[\\\"mute_survey_option\\\",\\\"7\\\",1]],[\\\"Seen this ad multiple times\\\",[\\\"mute_survey_option\\\",\\\"2\\\",1]],[\\\"Ad was inappropriate\\\",[\\\"mute_survey_option\\\",\\\"8\\\",1]],[\\\"Ad covered content\\\",[\\\"mute_survey_option\\\",\\\"3\\\",1]]]],[\\\"user_feedback_undo\\\",\\\"1\\\",1]]],[\\\"https://googleads.g.doubleclick.net/pagead/images/adchoices/iconx2-000000.png\\\",\\\"AdChoices\\\",\\\"Ad closed by %1$s\\\",0,\\\"https://www.gstatic.com/images/branding/googlelogo/2x/googlelogo_dark_color_84x28dp.png\\\",\\\"Stop seeing this ad\\\",\\\"We'll try not to show that ad again\\\",null,null,null,\\\"https://googleads.g.doubleclick.net/pagead/images/abg/iconx2-000000.png\\\",\\\"Ads by Google\\\",null,\\\"See my Google ad settings\\\",null,\\\"https://www.gstatic.com\\\",\\\"\\\",\\\"Ads by %1$s\\\",\\\"Ad settings\\\",\\\"https://adssettings.google.com\\\",\\\"Political Ad\\\",0],\\\"AB3afGEAAAFXW1tbW10sW251bGwsbnVsbCxbbnVsbCxudWxsLG51bGwsImh0dHBzOi8vZGlzcGxheWFkcy1mb3JtYXRzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hZHMvcHJldmlldy9jb250ZW50LmpzP2NsaWVudD13dGFcdTAwMjZvYmZ1c2NhdGVkQ3VzdG9tZXJJZD01NTYxMzM5NDM3XHUwMDI2Y3JlYXRpdmVJZD0zNTQwOTUwMjE1MDVcdTAwMjZ2ZXJzaW9uSWQ9MVx1MDAyNmFkR3JvdXBDcmVhdGl2ZUlkPTMxNjkxNjYzMTA2Mlx1MDAyNmh0bWxQYXJlbnRJZD1wcmV2LTFcdTAwMjZoZWlnaHQ9MFx1MDAyNndpZHRoPTBcdTAwMjZzaWc9QUNpVkJfeGVHemhFNDJiYWtRQ0dRbWRuNXZxU3lmbUNsZyJdXV1dLFsxLDJdXRR-61Yi-an6KhZi4ofQTX0H9y6cQkqBTu_y5qzXAuiZ8dqZWqf-OGrlhMYMqlkTsDP9B9gvk_H01VcSr-U4uqRp1ujVSY_3JH72yf_YpnZKPR-udMfp09jBZyJpLk_TTRZGZ0LlGnjKC7w5gTyNN5BgRLsMes5mw33A8cP9n066avQF_PMDU4riHT9AoNeTIDCm5KsbNsx_3noJ6fqrVavx75DXN43wAdT6fBCrOyBBm0ZfNJ5E7JBlrfKXVT-cL2VkkHj5jIjHaXj_xBIgzTwdtyKuWzDgN0KGf640uXNjNGHxKY__6Up_czrdEqcqs0l11faurOnHZ_YTiVt-Sq0,m_2FlvsgJUokxDd5UqPLkg\\\",\\\"https://adssettings.google.com/whythisad?reasons=AB3afGEAAAFXW1tbW10sW251bGwsbnVsbCxbbnVsbCxudWxsLG51bGwsImh0dHBzOi8vZGlzcGxheWFkcy1mb3JtYXRzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hZHMvcHJldmlldy9jb250ZW50LmpzP2NsaWVudD13dGFcdTAwMjZvYmZ1c2NhdGVkQ3VzdG9tZXJJZD01NTYxMzM5NDM3XHUwMDI2Y3JlYXRpdmVJZD0zNTQwOTUwMjE1MDVcdTAwMjZ2ZXJzaW9uSWQ9MVx1MDAyNmFkR3JvdXBDcmVhdGl2ZUlkPTMxNjkxNjYzMTA2Mlx1MDAyNmh0bWxQYXJlbnRJZD1wcmV2LTFcdTAwMjZoZWlnaHQ9MFx1MDAyNndpZHRoPTBcdTAwMjZzaWc9QUNpVkJfeGVHemhFNDJiYWtRQ0dRbWRuNXZxU3lmbUNsZyJdXV1dLFsxLDJdXRR-61Yi-an6KhZi4ofQTX0H9y6cQkqBTu_y5qzXAuiZ8dqZWqf-OGrlhMYMqlkTsDP9B9gvk_H01VcSr-U4uqRp1ujVSY_3JH72yf_YpnZKPR-udMfp09jBZyJpLk_TTRZGZ0LlGnjKC7w5gTyNN5BgRLsMes5mw33A8cP9n066avQF_PMDU4riHT9AoNeTIDCm5KsbNsx_3noJ6fqrVavx75DXN43wAdT6fBCrOyBBm0ZfNJ5E7JBlrfKXVT-cL2VkkHj5jIjHaXj_xBIgzTwdtyKuWzDgN0KGf640uXNjNGHxKY__6Up_czrdEqcqs0l11faurOnHZ_YTiVt-Sq0,m_2FlvsgJUokxDd5UqPLkg\\\\u0026source=display\\\\u0026cbt=HR2j8IIKFT4IlozQzZwJEO_bzY4GGJXpv2lSCjExNzYwMTE2NDJwAQ\\\\u0026cv=https://googleads.g.doubleclick.net/pagead/conversion/%3Fai%3DC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ%26sigh%3DW6w-9snWChI%26cid%3DCAQSWgDwy9IZiMbB4y5-_VohWM5rkfUKpjx1lXYjisehoQmA2_AjPg25rEU82XtVB-1s3L6cfbuwnZfMoHluY7KXQDEXUcRlMDOfD3sCGRYQZ2W7TfPoeWnqtJQ2Xg\\\",\\\"Why this ad?\\\",0],null,null,0,1,0,0,1,1,1,0,1,0,0,0,null,0,1,0,null,null,null,null,0,1,0,0,null,0,\\\"right\\\"]);\\u003c/script\\u003e\\u003c/body\\u003e\\u003c/html\\u003e\",\"auto_collect_location\":true,\"auto_protection_configuration\":{\"enable_protection\":false,\"reporting_url\":\"https://pagead2.googlesyndication.com/pagead/gen_204?id=gmob-apps\\u0026event=auto-protection\\u0026type=prevented-creative-action\\u0026qid=CNnuu_Tlv-UCFcX47QodTJIFWQ\\u0026creatives=crid0:354095021505,agcid0:316916631062,ctype0:28,ctid0:433,cuid0:221246613,lp0:/aclk%3Fsa%3DL%26ai%3DC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ%26ae%3D1%26num%3D1%26cid%3DCAASUeRoXbfm-muHmdVBigpult8aj8xgkIVSz0WxTJURc_UCNQZhZEi8J-m3ue7XRpZJftpK7rBRblzlxFqJ9BVnofHBAODJRko7oidb9N9SA4AWvg%26sig%3DAOD64_12K2xdOpcUJsRpv0Da0OP49oOi7g%26adurl%3Dhttps://itunes.apple.com/app/id1176011642%253Fmt%253D8%2526gclid%253DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE,loginfo0:C8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\u0026creatives_pb=Cu4ECBwQwfvUjacKGJaM0M2cCSCxAyiV6b9pMtMEQzhuRUg0RS0zWGRuLURNWHh0d2ZNcEpiSUJmUF8wOTlabG96UXpad0o1S3ZwNWJnQkVBRWd0ZW1pYm1EMGdfc05vQUgtNk9YeUFxZ0RBY2dEQ0tvRW93RlAwRWZrTmc3NlI1dEh2RmNJeXNaelREZG96WC1SM2hWNGVfcDREWWIxblAzak5QQXhfWFg4Y19FVjFENU1YNV84WllkeUJFdXhkdDFXcG5JU1A0YkJOd1lFX1RYZEQtTWlkUVVuOTlFb0NYTG9ISmd5ZGhIOV9ma2U0TlliazVEUEtDYWNjS1UwbVVwempDVFdDRVRIMTFRQTUwNWQ2NFJmY3lDTzV4Z1JTYTBTb0VRcktmUFJhaU5ZWnJSTktWdXlmX09xZmlJMDRsSkZiWVZHQWlJV3ljM3Z3QVM2bzUzNjd3R0lCZV9ielk0R2tBWUJvQVljZ0FmN2hxRnBtQWNCcUFlbXZodW9COVhKRzZnSG9RR29CNjNLRzZnSHJzMGJxQWZaeXh1b0I4X01HNmdIODlFYnFBZnMxUnU0QjVLbGl1N0UtZFR1MlFIQUI5cllBOEFINnRnRHdBZnMyQVBZQndINkJ4OWpiMjB1WVd4cFpXNHVjMmh2YjNSbGNpNW5ZV3hoZUhrdVlYUjBZV05ybUFnQm9BanY0ejZ3Q0FLNENBSFNDQWNJZ0dFUUFSZ2RzUWxEZnFLSElnRnBPcEFMQmJnVHNRT0NGQjBhRzIxdlltbHNaV0Z3Y0RvNk1TMWtkVzF0ZVhoaWFXUmhjSEJwWklnVUFiQVZBUQ\\u0026debugDialog=AdGroup%2BCreative%2BID%2B0%3D316916631062%26AdGroup%2BID%2B0%3D64412406202%26Backend%2BQuery%2BID%3D4E-3Xdn-DMXxtwfMpJbIBQ%26Creative%2BID%2B0%3D354095021505%26Customer%2BID%2B0%3D221246613%26Landing%2BPage%2B0%3Dhttps://itunes.apple.com/app/id1176011642%253Fmt%253D8%2526gclid%253DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE%26URL%2BClickstring%2B0%3DC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\u0026navigationURL={NAVIGATION_URL}\",\"reporting_urls\":[\"https://pagead2.googlesyndication.com/pagead/gen_204?id=gmob-apps\\u0026event=auto-protection\\u0026type=prevented-creative-action\\u0026qid=CNnuu_Tlv-UCFcX47QodTJIFWQ\\u0026creatives=crid0:354095021505,agcid0:316916631062,ctype0:28,ctid0:433,cuid0:221246613,lp0:/aclk%3Fsa%3DL%26ai%3DC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ%26ae%3D1%26num%3D1%26cid%3DCAASUeRoXbfm-muHmdVBigpult8aj8xgkIVSz0WxTJURc_UCNQZhZEi8J-m3ue7XRpZJftpK7rBRblzlxFqJ9BVnofHBAODJRko7oidb9N9SA4AWvg%26sig%3DAOD64_12K2xdOpcUJsRpv0Da0OP49oOi7g%26adurl%3Dhttps://itunes.apple.com/app/id1176011642%253Fmt%253D8%2526gclid%253DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE,loginfo0:C8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\u0026creatives_pb=Cu4ECBwQwfvUjacKGJaM0M2cCSCxAyiV6b9pMtMEQzhuRUg0RS0zWGRuLURNWHh0d2ZNcEpiSUJmUF8wOTlabG96UXpad0o1S3ZwNWJnQkVBRWd0ZW1pYm1EMGdfc05vQUgtNk9YeUFxZ0RBY2dEQ0tvRW93RlAwRWZrTmc3NlI1dEh2RmNJeXNaelREZG96WC1SM2hWNGVfcDREWWIxblAzak5QQXhfWFg4Y19FVjFENU1YNV84WllkeUJFdXhkdDFXcG5JU1A0YkJOd1lFX1RYZEQtTWlkUVVuOTlFb0NYTG9ISmd5ZGhIOV9ma2U0TlliazVEUEtDYWNjS1UwbVVwempDVFdDRVRIMTFRQTUwNWQ2NFJmY3lDTzV4Z1JTYTBTb0VRcktmUFJhaU5ZWnJSTktWdXlmX09xZmlJMDRsSkZiWVZHQWlJV3ljM3Z3QVM2bzUzNjd3R0lCZV9ielk0R2tBWUJvQVljZ0FmN2hxRnBtQWNCcUFlbXZodW9COVhKRzZnSG9RR29CNjNLRzZnSHJzMGJxQWZaeXh1b0I4X01HNmdIODlFYnFBZnMxUnU0QjVLbGl1N0UtZFR1MlFIQUI5cllBOEFINnRnRHdBZnMyQVBZQndINkJ4OWpiMjB1WVd4cFpXNHVjMmh2YjNSbGNpNW5ZV3hoZUhrdVlYUjBZV05ybUFnQm9BanY0ejZ3Q0FLNENBSFNDQWNJZ0dFUUFSZ2RzUWxEZnFLSElnRnBPcEFMQmJnVHNRT0NGQjBhRzIxdlltbHNaV0Z3Y0RvNk1TMWtkVzF0ZVhoaWFXUmhjSEJwWklnVUFiQVZBUQ\\u0026debugDialog=AdGroup%2BCreative%2BID%2B0%3D316916631062%26AdGroup%2BID%2B0%3D64412406202%26Backend%2BQuery%2BID%3D4E-3Xdn-DMXxtwfMpJbIBQ%26Creative%2BID%2B0%3D354095021505%26Customer%2BID%2B0%3D221246613%26Landing%2BPage%2B0%3Dhttps://itunes.apple.com/app/id1176011642%253Fmt%253D8%2526gclid%253DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE%26URL%2BClickstring%2B0%3DC8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\\u0026navigationURL={NAVIGATION_URL}\"]},\"content_url_opted_out\":true,\"content_vertical_opted_out\":true,\"debug_dialog\":\"AdGroup+Creative+ID+0=316916631062\\u0026AdGroup+ID+0=64412406202\\u0026Backend+Query+ID=4E-3Xdn-DMXxtwfMpJbIBQ\\u0026Creative+ID+0=354095021505\\u0026Customer+ID+0=221246613\\u0026Landing+Page+0=https://itunes.apple.com/app/id1176011642%3Fmt%3D8%26gclid%3DEAIaIQobChMI2e679OW_5QIVxfjtCh1MkgVZEAEYASAAEgJqz_D_BwE\\u0026URL+Clickstring+0=C8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\",\"gws_query_id\":\"4E-3XdaEC7OCtgezhL74Bg\",\"impression_urls\":[\"https://pagead2.googleadservices.com/pagead/adview?ai=Chwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\u0026sigh=xm3-cLv2uRo\\u0026cid=CAQSWgDwy9IZiMbB4y5-_VohWM5rkfUKpjx1lXYjisehoQmA2_AjPg25rEU82XtVB-1s3L6cfbuwnZfMoHluY7KXQDEXUcRlMDOfD3sCGRYQZ2W7TfPoeWnqtJQ2Xg\\u0026cmd=Ch5jYS1tYi1hcHAtcHViLTQ0ODczNzk0NjYxNTIyMTUQsQMYAQ\\u0026gvr=1\"],\"orientation\":\"portrait\",\"safe_browsing\":{\"allowed_headers\":[\"referer\"],\"click_string\":\"C8nEH4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEowFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI04lJFbYVGAiIWyc3vwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAbAVAQ\",\"header_to_scrub\":[\"cookie\"],\"malicious_reporting_enabled\":true,\"non_malicious_reporting_enabled\":false,\"protection_enabled\":false,\"rendered_ad_enabled\":false,\"report_url\":\"https://sb-ssl.google.com/safebrowsing/clientreport/malware\"}},\"adapters\":[\"GADMAdapterGoogleAdMobAds\"],\"data\":{\"pubid\":\"ca-mb-app-pub-4487379466152215//cak=no_cache\\u0026cadc=dg\\u0026caqid=4E-3XdaEC7OCtgezhL74Bg\"},\"fill_urls\":[\"https://googleads.g.doubleclick.net/pagead/conversion/?ai=Chwhg4E-3Xdn-DMXxtwfMpJbIBfP_099ZlozQzZwJ5Kvp5bgBEAEgtemibmD0g_sNoAH-6OXyAqgDAcgDCKoEoAFP0EfkNg76R5tHvFcIysZzTDdozX-R3hV4e_p4DYb1nP3jNPAx_XX8c_EV1D5MX5_8ZYdyBEuxdt1WpnISP4bBNwYE_TXdD-MidQUn99EoCXLoHJgydhH9_fke4NYbk5DPKCaccKU0mUpzjCTWCETH11QA505d64RfcyCO5xgRSa0SoEQrKfPRaiNYZrRNKVuyf_OqfiI0oFBB_yyy5XbwwAS6o5367wGIBe_bzY4GkAYBoAYcgAf7hqFpmAcBqAemvhuoB9XJG6gHoQGoB63KG6gHrs0bqAfZyxuoB8_MG6gH89EbqAfs1Ru4B5Kliu7E-dTu2QHAB9rYA8AH6tgDwAfs2APYBwH6Bx9jb20uYWxpZW4uc2hvb3Rlci5nYWxheHkuYXR0YWNrmAgBoAjv4z6wCAK4CAHSCAcIgGEQARgdsQlDfqKHIgFpOpALBbgTsQOCFB0aG21vYmlsZWFwcDo6MS1kdW1teXhiaWRhcHBpZIgUAQ\\u0026sigh=PGsAu-tzATA\\u0026label=drx_mediation_request_fill_result\\u0026mediation_fill_status=+gw_adnetstatus+\\u0026cid=CAQSWgDwy9IZiMbB4y5-_VohWM5rkfUKpjx1lXYjisehoQmA2_AjPg25rEU82XtVB-1s3L6cfbuwnZfMoHluY7KXQDEXUcRlMDOfD3sCGRYQZ2W7TfPoeWnqtJQ2Xg\"],\"id\":\"\"}],\"settings\":{\"click_urls\":[\"+gw_adnetid+\"],\"imp_urls\":[],\"nofill_urls\":[],\"ad_network_timeout_millis\":60000},\"request_id\":\"0\",\"backend_query_id\":\"4E-3Xdn-DMXxtwfMpJbIBQ\"}"; +} \ No newline at end of file diff --git a/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/ScarAdapterTest.java b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/ScarAdapterTest.java new file mode 100644 index 00000000..e3bb398e --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/ScarAdapterTest.java @@ -0,0 +1,101 @@ +package com.unity3d.scar.adapter.v2000; + +import android.app.Activity; +import android.content.Context; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.GMAEvent; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static com.unity3d.scar.adapter.v2000.Constants.SCARExampleAdUnitId; +import static com.unity3d.scar.adapter.v2000.Constants.SCARExampleInterstitialAdString; + +@RunWith(MockitoJUnitRunner.class) +public class ScarAdapterTest { + private Context context = InstrumentationRegistry.getInstrumentation().getContext(); + + @Mock + IAdsErrorHandler _adsErrorHandlerMock; + + private ScarAdapter _scarAdapter = new ScarAdapter(_adsErrorHandlerMock); + + @Mock + ISignalCollectionListener _signalCollectionListenerMock; + + @Mock + IScarInterstitialAdListenerWrapper _scarInterstitialAdListenerWrapperMock; + + @Test + public void testScarAdapterGetSignals() { + _scarAdapter.getSCARSignals(context, new String[]{"video"}, new String[]{"rewarded"}, _signalCollectionListenerMock); + Mockito.verify(_signalCollectionListenerMock, Mockito.timeout(1000).times(1)).onSignalsCollected(Mockito.anyString()); + } + + @Test + public void testScarAdapterGetSignalsEmptyInterstitialPlacement() { + _scarAdapter.getSCARSignals(context, new String[0], new String[]{"rewarded"}, _signalCollectionListenerMock); + Mockito.verify(_signalCollectionListenerMock, Mockito.timeout(1000).times(1)).onSignalsCollected(Mockito.anyString()); + } + + @Test + public void testScarAdapterGetSignalsEmptyRewardedPlacement() { + _scarAdapter.getSCARSignals(context, new String[]{"video"}, new String[0], _signalCollectionListenerMock); + Mockito.verify(_signalCollectionListenerMock, Mockito.timeout(1000).times(1)).onSignalsCollected(Mockito.anyString()); + } + + @Test + public void testScarAdapterLoad() { + _scarAdapter.getSCARSignals(context, new String[]{"video"}, new String[0], _signalCollectionListenerMock); + Mockito.verify(_signalCollectionListenerMock, Mockito.timeout(1000).times(1)).onSignalsCollected(Mockito.anyString()); + _scarAdapter.loadInterstitialAd(context, getDefaultScarMeta(), _scarInterstitialAdListenerWrapperMock); + Mockito.verify(_scarInterstitialAdListenerWrapperMock, Mockito.timeout(5000).times(1)).onAdLoaded(); + } + + @Test + public void testScarAdapterLoadAndShow() { + _scarAdapter.getSCARSignals(context, new String[]{"video"}, new String[0], _signalCollectionListenerMock); + Mockito.verify(_signalCollectionListenerMock, Mockito.timeout(1000).times(1)).onSignalsCollected(Mockito.anyString()); + _scarAdapter.loadInterstitialAd(context, getDefaultScarMeta(), _scarInterstitialAdListenerWrapperMock); + Mockito.verify(_scarInterstitialAdListenerWrapperMock, Mockito.timeout(5000).times(1)).onAdLoaded(); + try(ActivityScenario scenario = ActivityScenario.launch(Activity.class)) { + scenario.onActivity(new ActivityScenario.ActivityAction() { + @Override + public void perform(Activity activity) { + _scarAdapter.show(activity, "", "video"); + } + }); + } + Mockito.verify(_scarInterstitialAdListenerWrapperMock, Mockito.timeout(20000).times(1)).onAdOpened(); + Mockito.verify(_scarInterstitialAdListenerWrapperMock, Mockito.timeout(20000).times(1)).onAdImpression(); + } + + @Test + public void testScarAdapterShowWithoutLoad() { + _scarAdapter = new ScarAdapter(_adsErrorHandlerMock); + try(ActivityScenario scenario = ActivityScenario.launch(Activity.class)) { + scenario.onActivity(new ActivityScenario.ActivityAction() { + @Override + public void perform(Activity activity) { + _scarAdapter.show(activity, "", "video"); + } + }); + } + Mockito.verify(_adsErrorHandlerMock, Mockito.timeout(20000).times(1)).handleError(Mockito.any(GMAAdsError.class)); + } + + private ScarAdMetadata getDefaultScarMeta() { + return new ScarAdMetadata("video", "", SCARExampleAdUnitId, SCARExampleInterstitialAdString, 30); + } +} \ No newline at end of file diff --git a/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoCallbackTest.java b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoCallbackTest.java new file mode 100644 index 00000000..f6dd598a --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoCallbackTest.java @@ -0,0 +1,39 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import com.google.android.gms.ads.query.QueryInfo; +import com.unity3d.scar.adapter.common.DispatchGroup; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class QueryInfoCallbackTest { + @Mock + QueryInfoMetadata _queryInfoMetadataMock; + + @Mock + DispatchGroup _dispatchGroupMock; + + @Mock + QueryInfo _queryInfoMock; + + @Test + public void testQueryInfoCallbackSuccess() { + QueryInfoCallback queryInfoCallback = new QueryInfoCallback(_queryInfoMetadataMock, _dispatchGroupMock); + queryInfoCallback.onSuccess(_queryInfoMock); + Mockito.verify(_dispatchGroupMock, Mockito.times(1)).leave(); + Mockito.verify(_queryInfoMetadataMock, Mockito.times(1)).setQueryInfo(_queryInfoMock); + } + + @Test + public void testQueryInfoCallbackError() { + QueryInfoCallback queryInfoCallback = new QueryInfoCallback(_queryInfoMetadataMock, _dispatchGroupMock); + queryInfoCallback.onFailure(""); + Mockito.verify(_dispatchGroupMock, Mockito.times(1)).leave(); + Mockito.verify(_queryInfoMetadataMock, Mockito.times(1)).setError(Mockito.anyString()); + + } +} diff --git a/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/SignalsReaderTest.java b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/SignalsReaderTest.java new file mode 100644 index 00000000..62fd1986 --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/SignalsReaderTest.java @@ -0,0 +1,48 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Matchers.any; + +@RunWith(MockitoJUnitRunner.class) +public class SignalsReaderTest { + private Context context = InstrumentationRegistry.getInstrumentation().getContext(); + private ISignalCollectionListener _signalCollectionListener; + + @Before + public void before() { + _signalCollectionListener = Mockito.mock(ISignalCollectionListener.class); + } + + @Test + public void testGetScarSignals() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{"video"}, new String[]{"rewarded"}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + + @Test + public void testGetScarSignalsNoRewarded() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{"video"}, new String[]{}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + + @Test + public void testGetScarSignalsNoInterstitial() { + SignalsReader signalsReader = new SignalsReader(new SignalsStorage()); + signalsReader.getSCARSignals(context, new String[]{}, new String[]{"rewarded"}, _signalCollectionListener); + Mockito.verify(_signalCollectionListener, Mockito.timeout(5000).times(1)).onSignalsCollected(any(String.class)); + } + +} diff --git a/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/SignalsStorageTest.java b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/SignalsStorageTest.java new file mode 100644 index 00000000..f7f27461 --- /dev/null +++ b/unity-scaradapter-2000/src/androidTest/java/com/unity3d/scar/adapter/v2000/signals/SignalsStorageTest.java @@ -0,0 +1,21 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SignalsStorageTest { + @Mock + QueryInfoMetadata _queryInfoMetadaMock; + + @Test + public void testSignalStorage() { + SignalsStorage signalsStorage = new SignalsStorage(); + signalsStorage.put("video", _queryInfoMetadaMock); + QueryInfoMetadata storedMetadata = signalsStorage.getQueryInfoMetadata("video"); + Assert.assertEquals(_queryInfoMetadaMock, storedMetadata); + } +} diff --git a/unity-scaradapter-2000/src/main/AndroidManifest.xml b/unity-scaradapter-2000/src/main/AndroidManifest.xml new file mode 100644 index 00000000..74bc7d2a --- /dev/null +++ b/unity-scaradapter-2000/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/ScarAdapter.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/ScarAdapter.java new file mode 100644 index 00000000..edc9610d --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/ScarAdapter.java @@ -0,0 +1,59 @@ +package com.unity3d.scar.adapter.v2000; + +import android.content.Context; + +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarAdapter; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.ScarAdapterBase; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v2000.scarads.ScarInterstitialAd; +import com.unity3d.scar.adapter.v2000.scarads.ScarRewardedAd; +import com.unity3d.scar.adapter.v2000.signals.SignalsReader; +import com.unity3d.scar.adapter.v2000.signals.SignalsStorage; + +import static com.unity3d.scar.adapter.common.Utils.runOnUiThread; + +public class ScarAdapter extends ScarAdapterBase implements IScarAdapter { + + private SignalsStorage _scarSignalStorage; + + public ScarAdapter(IAdsErrorHandler adsErrorHandler) { + super(adsErrorHandler); + _scarSignalStorage = new SignalsStorage(); + _scarSignalReader = new SignalsReader(_scarSignalStorage); + } + + public void loadInterstitialAd(Context context, final ScarAdMetadata scarAd, final IScarInterstitialAdListenerWrapper adListenerWrapper) { + final ScarInterstitialAd interstitialAd = new ScarInterstitialAd(context, _scarSignalStorage.getQueryInfoMetadata(scarAd.getPlacementId()), scarAd, _adsErrorHandler, adListenerWrapper); + runOnUiThread(new Runnable() { + @Override + public void run() { + interstitialAd.loadAd(new IScarLoadListener() { + @Override + public void onAdLoaded() { + _loadedAds.put(scarAd.getPlacementId(), interstitialAd); + } + }); + } + }); + } + + public void loadRewardedAd(Context context, final ScarAdMetadata scarAd, final IScarRewardedAdListenerWrapper adListenerWrapper) { + final ScarRewardedAd rewardedAd = new ScarRewardedAd(context, _scarSignalStorage.getQueryInfoMetadata(scarAd.getPlacementId()), scarAd, _adsErrorHandler, adListenerWrapper); + runOnUiThread(new Runnable() { + @Override + public void run() { + rewardedAd.loadAd(new IScarLoadListener() { + @Override + public void onAdLoaded() { + _loadedAds.put(scarAd.getPlacementId(), rewardedAd); + } + }); + } + }); + } + +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarAdBase.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarAdBase.java new file mode 100644 index 00000000..2eb3a1e2 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarAdBase.java @@ -0,0 +1,48 @@ +package com.unity3d.scar.adapter.v2000.scarads; + +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.AdInfo; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.scarads.IScarAd; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v2000.signals.QueryInfoMetadata; + +public abstract class ScarAdBase implements IScarAd { + + protected T _adObj; + protected Context _context; + protected ScarAdMetadata _scarAdMetadata; + protected QueryInfoMetadata _queryInfoMetadata; + protected ScarAdListener _scarAdListener; + protected IAdsErrorHandler _adsErrorHandler; + + public ScarAdBase(Context context, ScarAdMetadata scarAdMetadata, QueryInfoMetadata queryInfoMetadata, IAdsErrorHandler adsErrorHandler) { + _context = context; + _scarAdMetadata = scarAdMetadata; + _queryInfoMetadata = queryInfoMetadata; + _adsErrorHandler = adsErrorHandler; + } + + public void setGmaAd(T rewardedAd) { + _adObj = rewardedAd; + } + + @Override + public void loadAd(IScarLoadListener loadListener) { + if (_queryInfoMetadata != null) { + AdInfo adInfo = new AdInfo(_queryInfoMetadata.getQueryInfo(), _scarAdMetadata.getAdString()); + AdRequest adRequest = new AdRequest.Builder().setAdInfo(adInfo).build(); + _scarAdListener.setLoadListener(loadListener); + loadAdInternal(adRequest, loadListener); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalLoadError(_scarAdMetadata)); + } + } + + protected abstract void loadAdInternal(AdRequest adRequest, IScarLoadListener loadListener); + +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarAdListener.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarAdListener.java new file mode 100644 index 00000000..7f52c5b7 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarAdListener.java @@ -0,0 +1,12 @@ +package com.unity3d.scar.adapter.v2000.scarads; + +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; + +public class ScarAdListener { + protected IScarLoadListener _loadListener; + + public void setLoadListener(IScarLoadListener loadListener) { + _loadListener = loadListener; + } + +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarInterstitialAd.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarInterstitialAd.java new file mode 100644 index 00000000..64de8853 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarInterstitialAd.java @@ -0,0 +1,35 @@ +package com.unity3d.scar.adapter.v2000.scarads; + +import android.app.Activity; +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.interstitial.InterstitialAd; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v2000.signals.QueryInfoMetadata; + +public class ScarInterstitialAd extends ScarAdBase { + + public ScarInterstitialAd(Context context, QueryInfoMetadata queryInfoMetadata, ScarAdMetadata scarAdMetadata, IAdsErrorHandler adsErrorHandler, IScarInterstitialAdListenerWrapper adListener) { + super(context, scarAdMetadata, queryInfoMetadata, adsErrorHandler); + _scarAdListener = new ScarInterstitialAdListener(adListener, this); + } + + @Override + protected void loadAdInternal(AdRequest adRequest, IScarLoadListener loadListener) { + InterstitialAd.load(_context, _scarAdMetadata.getAdUnitId(), adRequest, ((ScarInterstitialAdListener)_scarAdListener).getAdLoadListener()); + } + + @Override + public void show(Activity activity) { + if (_adObj != null) { + _adObj.show(activity); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalShowError(_scarAdMetadata)); + } + } +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarInterstitialAdListener.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarInterstitialAdListener.java new file mode 100644 index 00000000..36f7f2ac --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarInterstitialAdListener.java @@ -0,0 +1,70 @@ +package com.unity3d.scar.adapter.v2000.scarads; + +import androidx.annotation.NonNull; + +import com.google.android.gms.ads.AdError; +import com.google.android.gms.ads.FullScreenContentCallback; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.interstitial.InterstitialAd; +import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback; +import com.unity3d.scar.adapter.common.IScarInterstitialAdListenerWrapper; + +public class ScarInterstitialAdListener extends ScarAdListener { + private final ScarInterstitialAd _scarInterstitialAd; + private final IScarInterstitialAdListenerWrapper _adListenerWrapper; + + public ScarInterstitialAdListener(IScarInterstitialAdListenerWrapper adListenerWrapper, ScarInterstitialAd scarInterstitialAd) { + _adListenerWrapper = adListenerWrapper; + _scarInterstitialAd = scarInterstitialAd; + } + + private final InterstitialAdLoadCallback _adLoadCallback = new InterstitialAdLoadCallback() { + @Override + public void onAdLoaded(@NonNull InterstitialAd interstitialAd) { + super.onAdLoaded(interstitialAd); + _adListenerWrapper.onAdLoaded(); + interstitialAd.setFullScreenContentCallback(_fullScreenContentCallback); + _scarInterstitialAd.setGmaAd(interstitialAd); + if (_loadListener != null) { + _loadListener.onAdLoaded(); + } + } + + @Override + public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { + super.onAdFailedToLoad(loadAdError); + _adListenerWrapper.onAdFailedToLoad(loadAdError.getCode(), loadAdError.toString()); + } + }; + + private final FullScreenContentCallback _fullScreenContentCallback = new FullScreenContentCallback() { + @Override + public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) { + super.onAdFailedToShowFullScreenContent(adError); + _adListenerWrapper.onAdFailedToShow(adError.getCode(), adError.toString()); + } + + @Override + public void onAdShowedFullScreenContent() { + super.onAdShowedFullScreenContent(); + _adListenerWrapper.onAdOpened(); + } + + @Override + public void onAdDismissedFullScreenContent() { + super.onAdDismissedFullScreenContent(); + _adListenerWrapper.onAdClosed(); + } + + @Override + public void onAdImpression() { + super.onAdImpression(); + _adListenerWrapper.onAdImpression(); + } + }; + + public InterstitialAdLoadCallback getAdLoadListener() { + return _adLoadCallback; + } + +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarRewardedAd.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarRewardedAd.java new file mode 100644 index 00000000..6c4da1f1 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarRewardedAd.java @@ -0,0 +1,36 @@ +package com.unity3d.scar.adapter.v2000.scarads; + +import android.app.Activity; +import android.content.Context; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.rewarded.RewardedAd; +import com.unity3d.scar.adapter.common.GMAAdsError; +import com.unity3d.scar.adapter.common.IAdsErrorHandler; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; +import com.unity3d.scar.adapter.common.scarads.IScarLoadListener; +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.v2000.signals.QueryInfoMetadata; + +public class ScarRewardedAd extends ScarAdBase { + + public ScarRewardedAd(Context context, QueryInfoMetadata queryInfoMetadata, ScarAdMetadata scarAdMetadata, IAdsErrorHandler adsErrorHandler, IScarRewardedAdListenerWrapper adListener) { + super(context, scarAdMetadata, queryInfoMetadata, adsErrorHandler); + _scarAdListener = new ScarRewardedAdListener(adListener, this); + } + + @Override + protected void loadAdInternal(AdRequest adRequest, IScarLoadListener loadListener) { + RewardedAd.load(_context, _scarAdMetadata.getAdUnitId(), adRequest, ((ScarRewardedAdListener)_scarAdListener).getAdLoadListener()); + } + + @Override + public void show(Activity activity) { + if (_adObj != null) { + _adObj.show(activity, ((ScarRewardedAdListener)_scarAdListener).getOnUserEarnedRewardListener()); + } else { + _adsErrorHandler.handleError(GMAAdsError.InternalShowError(_scarAdMetadata)); + } + } + +} \ No newline at end of file diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarRewardedAdListener.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarRewardedAdListener.java new file mode 100644 index 00000000..c7f72c9a --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/scarads/ScarRewardedAdListener.java @@ -0,0 +1,84 @@ +package com.unity3d.scar.adapter.v2000.scarads; + +import androidx.annotation.NonNull; + +import com.google.android.gms.ads.AdError; +import com.google.android.gms.ads.FullScreenContentCallback; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.OnUserEarnedRewardListener; +import com.google.android.gms.ads.rewarded.RewardItem; +import com.google.android.gms.ads.rewarded.RewardedAd; +import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; +import com.unity3d.scar.adapter.common.IScarRewardedAdListenerWrapper; + +public class ScarRewardedAdListener extends ScarAdListener { + + private final ScarRewardedAd _scarRewardedAd; + private final IScarRewardedAdListenerWrapper _adListenerWrapper; + + public ScarRewardedAdListener(IScarRewardedAdListenerWrapper adListenerWrapper, ScarRewardedAd scarRewardedAd) { + _adListenerWrapper = adListenerWrapper; + _scarRewardedAd = scarRewardedAd; + } + + private final RewardedAdLoadCallback _adLoadCallback = new RewardedAdLoadCallback() { + @Override + public void onAdLoaded(@NonNull RewardedAd rewardedAd) { + super.onAdLoaded(rewardedAd); + _adListenerWrapper.onRewardedAdLoaded(); + rewardedAd.setFullScreenContentCallback(_fullScreenContentCallback); + _scarRewardedAd.setGmaAd(rewardedAd); + if (_loadListener != null) { + _loadListener.onAdLoaded(); + } + } + + @Override + public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { + super.onAdFailedToLoad(loadAdError); + _adListenerWrapper.onRewardedAdFailedToLoad(loadAdError.getCode(), loadAdError.toString()); + } + }; + + private final OnUserEarnedRewardListener _onUserEarnedRewardListener = new OnUserEarnedRewardListener() { + @Override + public void onUserEarnedReward(@NonNull RewardItem rewardItem) { + _adListenerWrapper.onUserEarnedReward(); + } + }; + + private final FullScreenContentCallback _fullScreenContentCallback = new FullScreenContentCallback() { + @Override + public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) { + super.onAdFailedToShowFullScreenContent(adError); + _adListenerWrapper.onRewardedAdFailedToShow(adError.getCode(), adError.toString()); + } + + @Override + public void onAdShowedFullScreenContent() { + super.onAdShowedFullScreenContent(); + _adListenerWrapper.onRewardedAdOpened(); + } + + @Override + public void onAdDismissedFullScreenContent() { + super.onAdDismissedFullScreenContent(); + _adListenerWrapper.onRewardedAdClosed(); + } + + @Override + public void onAdImpression() { + super.onAdImpression(); + _adListenerWrapper.onAdImpression(); + } + }; + + public OnUserEarnedRewardListener getOnUserEarnedRewardListener() { + return _onUserEarnedRewardListener; + } + + public RewardedAdLoadCallback getAdLoadListener() { + return _adLoadCallback; + } + +} \ No newline at end of file diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoCallback.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoCallback.java new file mode 100644 index 00000000..a9cb0f82 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoCallback.java @@ -0,0 +1,33 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import android.util.Log; + +import com.google.android.gms.ads.query.QueryInfo; +import com.google.android.gms.ads.query.QueryInfoGenerationCallback; +import com.unity3d.scar.adapter.common.DispatchGroup; +import com.unity3d.scar.adapter.v2000.ScarAdapter; + +public class QueryInfoCallback extends QueryInfoGenerationCallback { + + private DispatchGroup _dispatchGroup; + private QueryInfoMetadata _gmaQueryInfoMetadata; + + public QueryInfoCallback(final QueryInfoMetadata gmaQueryInfoMetadata, final DispatchGroup dispatchGroup) { + _dispatchGroup = dispatchGroup; + _gmaQueryInfoMetadata = gmaQueryInfoMetadata; + } + + // Called when QueryInfo generation succeeds + @Override + public void onSuccess(final QueryInfo queryInfo) { + _gmaQueryInfoMetadata.setQueryInfo(queryInfo); + _dispatchGroup.leave(); + } + + // Called when QueryInfo generation fails + @Override + public void onFailure(String failureMsg) { + _gmaQueryInfoMetadata.setError(failureMsg); + _dispatchGroup.leave(); + } +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoMetadata.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoMetadata.java new file mode 100644 index 00000000..30be0228 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/QueryInfoMetadata.java @@ -0,0 +1,41 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import com.google.android.gms.ads.query.QueryInfo; + +public class QueryInfoMetadata { + private String _placementId; + private QueryInfo _queryInfo; + private String _error; + + public QueryInfoMetadata(String placementId) { + _placementId = placementId; + } + + public String getPlacementId() { + return _placementId; + } + + public QueryInfo getQueryInfo() { + return _queryInfo; + } + + public String getQueryStr() { + String query = null; + if (_queryInfo != null) { + query = _queryInfo.getQuery(); + } + return query; + } + + public String getError() { + return _error; + } + + public void setQueryInfo(QueryInfo queryInfo) { + _queryInfo = queryInfo; + } + + public void setError(String error) { + _error = error; + } +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/SignalsReader.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/SignalsReader.java new file mode 100644 index 00000000..b5978fd4 --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/SignalsReader.java @@ -0,0 +1,86 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import android.content.Context; + +import com.google.android.gms.ads.AdFormat; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.QueryInfo; +import com.unity3d.scar.adapter.common.DispatchGroup; +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; +import com.unity3d.scar.adapter.common.signals.ISignalsReader; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsReader implements ISignalsReader { + private static Map _placementSignalMap; + private static SignalsStorage _signalsStorage; + + public SignalsReader(SignalsStorage signalsStorage) { + _signalsStorage = signalsStorage; + } + + @Override + public void getSCARSignals(Context context, String[] interstitialList, String[] rewardedList, + ISignalCollectionListener signalCompletionListener) { + DispatchGroup dispatchGroup = new DispatchGroup(); + + for (String interstitialId : interstitialList) { + dispatchGroup.enter(); + getSCARSignal(context, interstitialId, AdFormat.INTERSTITIAL, dispatchGroup); + } + + for (String rewardedId : rewardedList) { + dispatchGroup.enter(); + getSCARSignal(context, rewardedId, AdFormat.REWARDED, dispatchGroup); + } + + dispatchGroup.notify(new GMAScarDispatchCompleted(signalCompletionListener)); + } + + private void getSCARSignal(Context context, String placementId, AdFormat adType, DispatchGroup dispatchGroup) { + AdRequest request = new AdRequest.Builder().build(); + QueryInfoMetadata gmaQueryInfoMetadata = new QueryInfoMetadata(placementId); + QueryInfoCallback gmaQueryInfoCallback = new QueryInfoCallback(gmaQueryInfoMetadata, dispatchGroup); + // Callback on the QueryInfoCallback is async here. + _signalsStorage.put(placementId, gmaQueryInfoMetadata); + QueryInfo.generate(context, adType, request, gmaQueryInfoCallback); + } + + private class GMAScarDispatchCompleted implements Runnable { + + private ISignalCollectionListener _signalListener; + + public GMAScarDispatchCompleted(ISignalCollectionListener signalListener) { + _signalListener = signalListener; + } + + @Override + public void run() { + // Called once every dispatched thread has returned. + // Build up the JSON response since we received all the signals. + _placementSignalMap = new HashMap<>(); + String errorMessage = null; + for(Map.Entry queryInfoMetadata : _signalsStorage.getPlacementQueryInfoMap().entrySet()) { + QueryInfoMetadata currentQueryMetadata = queryInfoMetadata.getValue(); + _placementSignalMap.put(currentQueryMetadata.getPlacementId(), currentQueryMetadata.getQueryStr()); + if (currentQueryMetadata.getError() != null) { + // There is an error with one of the signals. + errorMessage = currentQueryMetadata.getError(); + } + } + + if (_placementSignalMap.size() > 0) { + JSONObject placementJSON = new JSONObject(_placementSignalMap); + _signalListener.onSignalsCollected(placementJSON.toString()); + } else if (errorMessage == null){ + _signalListener.onSignalsCollected(""); + } else { + // If no signals could be generated, send SIGNALS_ERROR with the last error message + _signalListener.onSignalsCollectionFailed(errorMessage); + } + } + } +} diff --git a/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/SignalsStorage.java b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/SignalsStorage.java new file mode 100644 index 00000000..ed7122bb --- /dev/null +++ b/unity-scaradapter-2000/src/main/java/com/unity3d/scar/adapter/v2000/signals/SignalsStorage.java @@ -0,0 +1,21 @@ +package com.unity3d.scar.adapter.v2000.signals; + +import java.util.HashMap; +import java.util.Map; + +public class SignalsStorage { + private Map _placementQueryInfoMap = new HashMap<>(); + + public Map getPlacementQueryInfoMap() { + return _placementQueryInfoMap; + } + + public QueryInfoMetadata getQueryInfoMetadata(String placementId) { + return _placementQueryInfoMap.get(placementId); + } + + public void put(String key, QueryInfoMetadata value) { + _placementQueryInfoMap.put(key, value); + } + +} diff --git a/unity-scaradapter-common/.gitignore b/unity-scaradapter-common/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/unity-scaradapter-common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/unity-scaradapter-common/build.gradle b/unity-scaradapter-common/build.gradle new file mode 100644 index 00000000..755099b2 --- /dev/null +++ b/unity-scaradapter-common/build.gradle @@ -0,0 +1,56 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 30 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + multiDexEnabled true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:2.28.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} + +task deleteOldJar(type: Delete) { + delete("../unity-ads/libs/${project.name}.jar") +} + +task copyJars(type: Copy) { + from('build/intermediates/compile_library_classes_jar/release/') + into('../unity-ads/libs/') + include('classes.jar') + rename('classes.jar', "${project.name}.jar") +} + +copyJars.dependsOn(deleteOldJar) + +project.tasks.whenTaskAdded { Task theTask -> + if (theTask.name == 'bundleLibCompileToJarRelease') { + theTask.finalizedBy(copyJars) + } +} \ No newline at end of file diff --git a/unity-scaradapter-common/consumer-rules.pro b/unity-scaradapter-common/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/unity-scaradapter-common/proguard-rules.pro b/unity-scaradapter-common/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/unity-scaradapter-common/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/unity-scaradapter-common/src/main/AndroidManifest.xml b/unity-scaradapter-common/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1d38930d --- /dev/null +++ b/unity-scaradapter-common/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/DispatchGroup.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/DispatchGroup.java new file mode 100644 index 00000000..f71e757c --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/DispatchGroup.java @@ -0,0 +1,34 @@ +package com.unity3d.scar.adapter.common; + +/** + * Utility class to {@link #enter} multiple thread and await for all of them to {@link #leave} to run a specific task. + */ +public class DispatchGroup { + + private int _threadCount = 0; + private Runnable _runnable; + + public DispatchGroup() { + _threadCount = 0; + } + + public synchronized void enter() { + _threadCount++; + } + + public synchronized void leave() { + _threadCount--; + notifyGroup(); + } + + public void notify(Runnable r) { + _runnable = r; + notifyGroup(); + } + + private void notifyGroup() { + if (_threadCount <=0 && _runnable !=null) { + _runnable.run(); + } + } +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/GMAAdsError.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/GMAAdsError.java new file mode 100644 index 00000000..2ff502d1 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/GMAAdsError.java @@ -0,0 +1,46 @@ +package com.unity3d.scar.adapter.common; + +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; + +public class GMAAdsError extends WebViewAdsError { + public static final String INTERNAL_SHOW_MESSAGE_NOT_LOADED = "Cannot show ad that is not loaded for placement %s"; + public static final String INTERNAL_LOAD_MESSAGE_MISSING_QUERYINFO = "Missing queryInfoMetadata for ad %s"; + + public GMAAdsError(GMAEvent errorCategory, Object... errorArguments) { + super(errorCategory, null, errorArguments); + } + + public GMAAdsError(GMAEvent errorCategory, String description, Object... errorArguments) { + super(errorCategory, description, errorArguments); + } + + @Override + public String getDomain() { + return "GMA"; + } + + + public static GMAAdsError NoAdsError(String placementId, String queryId, String message) { + return new GMAAdsError(GMAEvent.NO_AD_ERROR, message, placementId, queryId, message); + } + + public static GMAAdsError InternalShowError(ScarAdMetadata scarAdMetadata) { + return InternalShowError(scarAdMetadata, String.format(INTERNAL_SHOW_MESSAGE_NOT_LOADED, scarAdMetadata.getPlacementId())); + } + + public static GMAAdsError InternalShowError(ScarAdMetadata scarAdMetadata, String message) { + return new GMAAdsError(GMAEvent.INTERNAL_SHOW_ERROR, message, scarAdMetadata.getPlacementId(), scarAdMetadata.getQueryId(), message); + } + + public static GMAAdsError InternalLoadError(ScarAdMetadata scarAdMetadata) { + return InternalLoadError(scarAdMetadata, String.format(INTERNAL_LOAD_MESSAGE_MISSING_QUERYINFO, scarAdMetadata.getPlacementId())); + } + + public static GMAAdsError InternalLoadError(ScarAdMetadata scarAdMetadata, String message) { + return new GMAAdsError(GMAEvent.INTERNAL_LOAD_ERROR, message, scarAdMetadata.getPlacementId(), scarAdMetadata.getQueryId(), message); + } + + public static GMAAdsError InternalSignalsError(String message) { + return new GMAAdsError(GMAEvent.INTERNAL_SIGNALS_ERROR, message, message); + } +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/GMAEvent.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/GMAEvent.java new file mode 100644 index 00000000..5d8fe323 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/GMAEvent.java @@ -0,0 +1,27 @@ +package com.unity3d.scar.adapter.common; + +public enum GMAEvent { + INIT_SUCCESS, + INIT_ERROR, + VERSION, + SIGNALS, + SIGNALS_ERROR, + INTERNAL_SIGNALS_ERROR, + AD_LOADED, + INTERSTITIAL_IMPRESSION_RECORDED, + REWARDED_IMPRESSION_RECORDED, + INTERNAL_LOAD_ERROR, + LOAD_ERROR, + NO_AD_ERROR, + AD_STARTED, + INTERNAL_SHOW_ERROR, + REWARDED_SHOW_ERROR, + INTERSTITIAL_SHOW_ERROR, + FIRST_QUARTILE, + MIDPOINT, + AD_EARNED_REWARD, + AD_CLICKED, + AD_SKIPPED, + AD_CLOSED, + METHOD_ERROR +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IAdsErrorHandler.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IAdsErrorHandler.java new file mode 100644 index 00000000..a0c77969 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IAdsErrorHandler.java @@ -0,0 +1,5 @@ +package com.unity3d.scar.adapter.common; + +public interface IAdsErrorHandler { + void handleError(T unityError); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarAdapter.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarAdapter.java new file mode 100644 index 00000000..251c0283 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarAdapter.java @@ -0,0 +1,14 @@ +package com.unity3d.scar.adapter.common; + +import android.app.Activity; +import android.content.Context; + +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; + +public interface IScarAdapter { + void getSCARSignals(Context context, String[] interstitialList, String[] rewardedList, ISignalCollectionListener signalCompletionListener); + void loadInterstitialAd(Context context, ScarAdMetadata scarAdMetadata, IScarInterstitialAdListenerWrapper adListener); + void loadRewardedAd(Context context, ScarAdMetadata scarAdMetadata, IScarRewardedAdListenerWrapper adListener); + void show(Activity activity, String queryId, String placementId); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarInterstitialAdListenerWrapper.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarInterstitialAdListenerWrapper.java new file mode 100644 index 00000000..623ed31f --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarInterstitialAdListenerWrapper.java @@ -0,0 +1,47 @@ +package com.unity3d.scar.adapter.common; + +/** + * A listener for receiving notifications during the lifecycle of an ad. + */ +public interface IScarInterstitialAdListenerWrapper { + + /** + * Called when an ad is loaded. + */ + void onAdLoaded(); + + /** + * Called when an ad request failed to load. + */ + void onAdFailedToLoad(int errorCode, String errorString); + + /** + * Called when an interstitial ad opens. + */ + void onAdOpened(); + + /** + * Called when an ad failed to show on screen. + */ + void onAdFailedToShow(int errorCode, String errorString); + + /** + * Called when a click is recorded for an ad. + */ + void onAdClicked(); + + /** + * Called when the user has left the app. + */ + void onAdLeftApplication(); + + /** + * Called when the user is about to return to the application after clicking on an ad. + */ + void onAdClosed(); + + /** + * Called when an impression is recorded for an ad. + */ + void onAdImpression(); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarRewardedAdListenerWrapper.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarRewardedAdListenerWrapper.java new file mode 100644 index 00000000..304c6de7 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IScarRewardedAdListenerWrapper.java @@ -0,0 +1,42 @@ +package com.unity3d.scar.adapter.common; + +/** + * A listener for receiving notifications during the lifecycle of an ad. + */ +public interface IScarRewardedAdListenerWrapper { + + /** + * Called when a rewarded ad has loaded successfully. + */ + void onRewardedAdLoaded(); + + /** + * Called when a rewarded ad has failed to load. + */ + void onRewardedAdFailedToLoad(int errorCode, String errorString); + + /** + * Called when a rewarded ad opens. + */ + void onRewardedAdOpened(); + + /** + * Called when the rewarded ad fails to show. + */ + void onRewardedAdFailedToShow(int errorCode, String errorString); + + /** + * Called when a user earned a reward for watching the ad. + */ + void onUserEarnedReward(); + + /** + * Called when a rewarded ad is closed. + */ + void onRewardedAdClosed(); + + /** + * Called when an impression is recorded for an ad. + */ + void onAdImpression(); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IUnityAdsError.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IUnityAdsError.java new file mode 100644 index 00000000..6a051893 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/IUnityAdsError.java @@ -0,0 +1,7 @@ +package com.unity3d.scar.adapter.common; + +public interface IUnityAdsError { + String getDomain();//domain to represent a custom error or a class + String getDescription(); // description of the error, localized message + int getCode();// unique code per domain +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/ScarAdapterBase.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/ScarAdapterBase.java new file mode 100644 index 00000000..6512d6f0 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/ScarAdapterBase.java @@ -0,0 +1,47 @@ +package com.unity3d.scar.adapter.common; + +import android.app.Activity; +import android.content.Context; + +import com.unity3d.scar.adapter.common.scarads.IScarAd; +import com.unity3d.scar.adapter.common.signals.ISignalCollectionListener; +import com.unity3d.scar.adapter.common.signals.ISignalsReader; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static com.unity3d.scar.adapter.common.Utils.runOnUiThread; + +public abstract class ScarAdapterBase implements IScarAdapter { + protected ISignalsReader _scarSignalReader; + protected Map _loadedAds = new ConcurrentHashMap<>(); + protected IScarAd _currentAdReference; + protected IAdsErrorHandler _adsErrorHandler; + + public ScarAdapterBase(IAdsErrorHandler adsErrorHandler) { + _adsErrorHandler = adsErrorHandler; + } + + @Override + public void getSCARSignals(Context context, String[] interstitialList, String[] rewardedList, ISignalCollectionListener signalCompletionListener) { + _scarSignalReader.getSCARSignals(context, interstitialList, rewardedList, signalCompletionListener); + } + + @Override + public void show(final Activity activity, String queryId, String placementId) { + IScarAd scarAd = _loadedAds.get(placementId); + + if (scarAd == null) { + _adsErrorHandler.handleError(GMAAdsError.NoAdsError(placementId, queryId, "Could not find ad for placement '" + placementId + "'.")); + } else { + // We keep a reference to the ad so that we have it if a user leaves the app during show + _currentAdReference = scarAd; + runOnUiThread(new Runnable() { + @Override + public void run() { + _currentAdReference.show(activity); + } + }); + } + } +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/Utils.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/Utils.java new file mode 100644 index 00000000..04794294 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/Utils.java @@ -0,0 +1,16 @@ +package com.unity3d.scar.adapter.common; + +import android.os.Handler; +import android.os.Looper; + +public class Utils { + + public static void runOnUiThread(Runnable runnable) { + runOnUiThread(runnable, 0); + } + + public static void runOnUiThread(Runnable runnable, long delay) { + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(runnable, delay); + } +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/WebViewAdsError.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/WebViewAdsError.java new file mode 100644 index 00000000..93ba963d --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/WebViewAdsError.java @@ -0,0 +1,35 @@ +package com.unity3d.scar.adapter.common; + +public class WebViewAdsError implements IUnityAdsError { + protected String _description; + private Enum _errorCategory; + protected Object[] _errorArguments; + + public WebViewAdsError(GMAEvent errorCategory, String description, Object... errorArguments) { + _errorCategory = errorCategory; + _description = description; + _errorArguments = errorArguments; + } + + @Override + public String getDomain() { + return null; + } + + @Override + public String getDescription() { + return _description; + } + + @Override + public int getCode() { + return -1; + } + + public Enum getErrorCategory() { + return _errorCategory; + } + + public Object[] getErrorArguments() { return _errorArguments; } + +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/IScarAd.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/IScarAd.java new file mode 100644 index 00000000..3647ff1a --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/IScarAd.java @@ -0,0 +1,8 @@ +package com.unity3d.scar.adapter.common.scarads; + +import android.app.Activity; + +public interface IScarAd { + void loadAd(IScarLoadListener loadListener); + void show(Activity activity); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/IScarLoadListener.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/IScarLoadListener.java new file mode 100644 index 00000000..e4fe5566 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/IScarLoadListener.java @@ -0,0 +1,5 @@ +package com.unity3d.scar.adapter.common.scarads; + +public interface IScarLoadListener { + void onAdLoaded(); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/ScarAdMetadata.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/ScarAdMetadata.java new file mode 100644 index 00000000..b20b7d4e --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/scarads/ScarAdMetadata.java @@ -0,0 +1,43 @@ +package com.unity3d.scar.adapter.common.scarads; + +public class ScarAdMetadata { + + private String _placementId; + private String _queryId; + private String _adUnitId; + private String _adString; + private Integer _videoLengthMs; + + public ScarAdMetadata(String placementId, String queryId) { + this(placementId, queryId, null, null, null); + } + + public ScarAdMetadata(String placementId, String queryId, String adUnitId, String adString, Integer videoLengthMs) { + _placementId = placementId; + _queryId = queryId; + _adUnitId = adUnitId; + _adString = adString; + _videoLengthMs = videoLengthMs; + } + + public String getPlacementId() { + return _placementId; + } + + public String getQueryId() { + return _queryId; + } + + public String getAdUnitId() { + return _adUnitId; + } + + public String getAdString() { + return _adString; + } + + public Integer getVideoLengthMs() { + return _videoLengthMs; + } + +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/signals/ISignalCollectionListener.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/signals/ISignalCollectionListener.java new file mode 100644 index 00000000..4f70da11 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/signals/ISignalCollectionListener.java @@ -0,0 +1,6 @@ +package com.unity3d.scar.adapter.common.signals; + +public interface ISignalCollectionListener { + void onSignalsCollected(String signalsMap); + void onSignalsCollectionFailed(String errorMsg); +} diff --git a/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/signals/ISignalsReader.java b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/signals/ISignalsReader.java new file mode 100644 index 00000000..de9bdb66 --- /dev/null +++ b/unity-scaradapter-common/src/main/java/com/unity3d/scar/adapter/common/signals/ISignalsReader.java @@ -0,0 +1,8 @@ +package com.unity3d.scar.adapter.common.signals; + +import android.content.Context; + +public interface ISignalsReader { + void getSCARSignals(Context context, String[] interstitialList, String[] rewardedList, + ISignalCollectionListener signalCompletionListener); +} diff --git a/unity-scaradapter-common/src/test/java/com/unity3d/scar/adapter/common/DispatchGroupTest.java b/unity-scaradapter-common/src/test/java/com/unity3d/scar/adapter/common/DispatchGroupTest.java new file mode 100644 index 00000000..7f844bdc --- /dev/null +++ b/unity-scaradapter-common/src/test/java/com/unity3d/scar/adapter/common/DispatchGroupTest.java @@ -0,0 +1,61 @@ +package com.unity3d.scar.adapter.common; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class DispatchGroupTest { + private Runnable _mockedRunnable; + + @Before + public void initMocks() { + _mockedRunnable = Mockito.mock(Runnable.class); + } + + @Test + public void testDispatchGroupCompletedNotifyAfterLeave() { + DispatchGroup dispatchGroup = new DispatchGroup(); + dispatchGroup.enter(); + dispatchGroup.leave(); + dispatchGroup.notify(_mockedRunnable); + Mockito.verify(_mockedRunnable, Mockito.times(1)).run(); + } + + @Test + public void testDispatchGroupCompletedLeaveAfterNotify() { + DispatchGroup dispatchGroup = new DispatchGroup(); + dispatchGroup.enter(); + dispatchGroup.notify(_mockedRunnable); + dispatchGroup.leave(); + Mockito.verify(_mockedRunnable, Mockito.times(1)).run(); + } + + @Test + public void testDispatchGroupMultipleCycle() { + DispatchGroup dispatchGroup = new DispatchGroup(); + dispatchGroup.enter(); + dispatchGroup.enter(); + dispatchGroup.notify(_mockedRunnable); + dispatchGroup.leave(); + dispatchGroup.leave(); + Mockito.verify(_mockedRunnable, Mockito.times(1)).run(); + } + + @Test + public void testDispatchGroupMultipleCycleMissingOneLeave() { + DispatchGroup dispatchGroup = new DispatchGroup(); + dispatchGroup.enter(); + dispatchGroup.enter(); + dispatchGroup.notify(_mockedRunnable); + dispatchGroup.leave(); + Mockito.verify(_mockedRunnable, Mockito.times(0)).run(); + } + + @Test + public void testDispatchGroupNot() { + DispatchGroup dispatchGroup = new DispatchGroup(); + dispatchGroup.enter(); + dispatchGroup.notify(_mockedRunnable); + Mockito.verify(_mockedRunnable, Mockito.times(0)).run(); + } +} diff --git a/unity-scaradapter-common/src/test/java/com/unity3d/scar/adapter/common/GMAAdsErrorTest.java b/unity-scaradapter-common/src/test/java/com/unity3d/scar/adapter/common/GMAAdsErrorTest.java new file mode 100644 index 00000000..3c189260 --- /dev/null +++ b/unity-scaradapter-common/src/test/java/com/unity3d/scar/adapter/common/GMAAdsErrorTest.java @@ -0,0 +1,47 @@ +package com.unity3d.scar.adapter.common; + +import com.unity3d.scar.adapter.common.scarads.ScarAdMetadata; + +import org.junit.Assert; +import org.junit.Test; + +public class GMAAdsErrorTest { + private static final String TEST_PLACEMENT = "video"; + private static final String TEST_QUERYID = "query"; + private static final String TEST_MESSAGE = "error message"; + + @Test + public void testInternalShowError() { + ScarAdMetadata scarAdMetadata = new ScarAdMetadata(TEST_PLACEMENT, TEST_QUERYID); + GMAAdsError gmaAdsError = GMAAdsError.InternalShowError(scarAdMetadata); + String formattedErrorMessage = String.format(GMAAdsError.INTERNAL_SHOW_MESSAGE_NOT_LOADED, TEST_PLACEMENT); + validateGmaAdsError(gmaAdsError, GMAEvent.INTERNAL_SHOW_ERROR, formattedErrorMessage, TEST_PLACEMENT, TEST_QUERYID, formattedErrorMessage); + } + + @Test + public void testInternalLoadError() { + ScarAdMetadata scarAdMetadata = new ScarAdMetadata(TEST_PLACEMENT, TEST_QUERYID); + GMAAdsError gmaAdsError = GMAAdsError.InternalLoadError(scarAdMetadata); + String formattedErrorMessage = String.format(GMAAdsError.INTERNAL_LOAD_MESSAGE_MISSING_QUERYINFO, TEST_PLACEMENT); + validateGmaAdsError(gmaAdsError, GMAEvent.INTERNAL_LOAD_ERROR, formattedErrorMessage, TEST_PLACEMENT, TEST_QUERYID, formattedErrorMessage); + } + + @Test + public void testAdError() { + GMAAdsError gmaAdsError = GMAAdsError.NoAdsError(TEST_PLACEMENT, TEST_QUERYID, TEST_MESSAGE); + validateGmaAdsError(gmaAdsError, GMAEvent.NO_AD_ERROR, TEST_MESSAGE, TEST_PLACEMENT, TEST_QUERYID, TEST_MESSAGE); + } + + @Test + public void testSignalsError() { + GMAAdsError gmaAdsError = GMAAdsError.InternalSignalsError(TEST_MESSAGE); + validateGmaAdsError(gmaAdsError, GMAEvent.SIGNALS_ERROR, TEST_MESSAGE, TEST_MESSAGE); + } + + private void validateGmaAdsError(GMAAdsError gmaAdsError, Enum eventCategory, String description, Object... errorArguments) { + Assert.assertEquals("GMA", gmaAdsError.getDomain()); + Assert.assertEquals(eventCategory, gmaAdsError.getErrorCategory()); + Assert.assertArrayEquals(errorArguments, gmaAdsError.getErrorArguments()); + Assert.assertEquals(description, gmaAdsError.getDescription()); + } +}