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());
+ }
+}