diff --git a/sdk/src/main/java/com/uid2/data/UID2Identity.kt b/sdk/src/main/java/com/uid2/data/UID2Identity.kt
index ad94221..b4444de 100644
--- a/sdk/src/main/java/com/uid2/data/UID2Identity.kt
+++ b/sdk/src/main/java/com/uid2/data/UID2Identity.kt
@@ -34,6 +34,7 @@ data class UID2Identity(
* Helper function to parse a given JSON object into the expected UID2Identity instance. If the JSON instance
* doesn't contain all required parameters, then null is returned.
*/
+ @JvmStatic
fun fromJson(json: JSONObject): UID2Identity? {
val advertisingToken = json.opt("advertising_token")?.toString() ?: return null
val refreshToken = json.opt("refresh_token")?.toString() ?: return null
diff --git a/securesignals-ima-dev-app/build.gradle b/securesignals-ima-dev-app/build.gradle
new file mode 100644
index 0000000..cfc6cc1
--- /dev/null
+++ b/securesignals-ima-dev-app/build.gradle
@@ -0,0 +1,46 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+apply from: rootProject.file("$rootDir/common.gradle")
+
+android {
+ namespace 'com.uid2.dev'
+
+ defaultConfig {
+ applicationId "com.uid2.securesignals.ima.devapp"
+ minSdk 21
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ release {
+ shrinkResources true
+ minifyEnabled true
+ }
+ }
+
+ lint {
+ disable 'GradleDependency', 'IconDipSize', 'IconDensities', 'RtlEnabled'
+ }
+
+}
+
+dependencies {
+ implementation project(path: ':securesignals-ima')
+ compileOnly 'com.uid2:uid2-android-sdk:0.1.0'
+ implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0'
+
+ implementation 'androidx.core:core-ktx:1.10.0'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'androidx.activity:activity:1.7.0'
+ implementation 'com.google.android.material:material:1.8.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
+ implementation 'androidx.multidex:multidex:2.0.1'
+ implementation 'androidx.browser:browser:1.4.0'
+ implementation 'androidx.media:media:1.6.0'
+}
diff --git a/securesignals-ima-dev-app/lint-baseline.xml b/securesignals-ima-dev-app/lint-baseline.xml
new file mode 100644
index 0000000..f418c4a
--- /dev/null
+++ b/securesignals-ima-dev-app/lint-baseline.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/securesignals-ima-dev-app/src/main/AndroidManifest.xml b/securesignals-ima-dev-app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4293f34
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/securesignals-ima-dev-app/src/main/java/com/uid2/dev/IMADevApplication.java b/securesignals-ima-dev-app/src/main/java/com/uid2/dev/IMADevApplication.java
new file mode 100644
index 0000000..2753915
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/java/com/uid2/dev/IMADevApplication.java
@@ -0,0 +1,18 @@
+package com.uid2.dev;
+
+import android.app.Application;
+
+import com.uid2.UID2Manager;
+
+public class IMADevApplication extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Initialise the UID2Manager class. We will use it's DefaultNetworkSession rather than providing our own
+ // custom implementation. This can be done to allow wrapping something like OkHttp.
+ UID2Manager.init(this.getApplicationContext());
+ }
+
+}
diff --git a/securesignals-ima-dev-app/src/main/java/com/uid2/dev/MainActivity.java b/securesignals-ima-dev-app/src/main/java/com/uid2/dev/MainActivity.java
new file mode 100644
index 0000000..5b6243c
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/java/com/uid2/dev/MainActivity.java
@@ -0,0 +1,277 @@
+package com.uid2.dev;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.MediaController;
+import android.widget.VideoView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
+import com.google.ads.interactivemedia.v3.api.AdErrorEvent;
+import com.google.ads.interactivemedia.v3.api.AdEvent;
+import com.google.ads.interactivemedia.v3.api.AdsLoader;
+import com.google.ads.interactivemedia.v3.api.AdsManager;
+import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent;
+import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
+import com.google.ads.interactivemedia.v3.api.AdsRequest;
+import com.google.ads.interactivemedia.v3.api.ImaSdkFactory;
+import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
+import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
+import com.uid2.UID2Manager;
+import com.uid2.data.UID2Identity;
+
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+/**
+ * Porting of Google's BasicExample
+ * BasicExample
+ */
+public class MainActivity extends AppCompatActivity {
+
+ private static final String LOGTAG = "IMABasicSample";
+ private static final String SAMPLE_VIDEO_URL =
+ "https://storage.googleapis.com/gvabox/media/samples/stock.mp4";
+
+ /**
+ * IMA sample tag for a single skippable inline video ad. See more IMA sample tags at
+ * Client Side Tags
+ */
+ private static final String SAMPLE_VAST_TAG_URL =
+ "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/"
+ + "single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast"
+ + "&unviewed_position_start=1&env=vp&impl=s&correlator=";
+
+ // Factory class for creating SDK objects.
+ private ImaSdkFactory sdkFactory;
+
+ // The AdsLoader instance exposes the requestAds method.
+ private AdsLoader adsLoader;
+
+ // AdsManager exposes methods to control ad playback and listen to ad events.
+ private AdsManager adsManager;
+
+ // The saved content position, used to resumed content following an ad break.
+ private int savedPosition = 0;
+
+ // This sample uses a VideoView for content and ad playback. For production apps, Android's Exoplayer offers
+ // a more fully featured player compared to the VideoView.
+ private VideoView videoPlayer;
+ private MediaController mediaController;
+ private View playButton;
+ private VideoAdPlayerAdapter videoAdPlayerAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+
+ // Load UID2Identity to test with
+ loadUID2Identity();
+
+ // Create the UI for controlling the video view.
+ mediaController = new MediaController(this);
+ videoPlayer = findViewById(R.id.videoView);
+ mediaController.setAnchorView(videoPlayer);
+ videoPlayer.setMediaController(mediaController);
+
+ // Create an ad display container that uses a ViewGroup to listen to taps.
+ ViewGroup videoPlayerContainer = findViewById(R.id.videoPlayerContainer);
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ videoAdPlayerAdapter = new VideoAdPlayerAdapter(videoPlayer, audioManager);
+
+ sdkFactory = ImaSdkFactory.getInstance();
+
+ AdDisplayContainer adDisplayContainer =
+ ImaSdkFactory.createAdDisplayContainer(videoPlayerContainer, videoAdPlayerAdapter);
+
+ // Create an AdsLoader.
+ ImaSdkSettings settings = sdkFactory.createImaSdkSettings();
+ adsLoader = sdkFactory.createAdsLoader(this, settings, adDisplayContainer);
+
+ // Add listeners for when ads are loaded and for errors.
+ adsLoader.addAdErrorListener(
+ new AdErrorEvent.AdErrorListener() {
+ /** An event raised when there is an error loading or playing ads. */
+ @Override
+ public void onAdError(AdErrorEvent adErrorEvent) {
+ Log.i(LOGTAG, "Ad Error: " + adErrorEvent.getError().getMessage());
+ resumeContent();
+ }
+ });
+ adsLoader.addAdsLoadedListener(
+ new AdsLoader.AdsLoadedListener() {
+ @Override
+ public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {
+ // Ads were successfully loaded, so get the AdsManager instance. AdsManager has
+ // events for ad playback and errors.
+ adsManager = adsManagerLoadedEvent.getAdsManager();
+
+ // Attach event and error event listeners.
+ adsManager.addAdErrorListener(
+ new AdErrorEvent.AdErrorListener() {
+ /** An event raised when there is an error loading or playing ads. */
+ @Override
+ public void onAdError(AdErrorEvent adErrorEvent) {
+ Log.e(LOGTAG, "Ad Error: " + adErrorEvent.getError().getMessage());
+ String universalAdIds =
+ Arrays.toString(adsManager.getCurrentAd().getUniversalAdIds());
+ Log.i(
+ LOGTAG,
+ "Discarding the current ad break with universal "
+ + "ad Ids: "
+ + universalAdIds);
+ adsManager.discardAdBreak();
+ }
+ });
+ adsManager.addAdEventListener(
+ new AdEvent.AdEventListener() {
+ /** Responds to AdEvents. */
+ @Override
+ public void onAdEvent(AdEvent adEvent) {
+ if (adEvent.getType() != AdEvent.AdEventType.AD_PROGRESS) {
+ Log.i(LOGTAG, "Event: " + adEvent.getType());
+ }
+ // These are the suggested event types to handle. For full list of all ad event types,
+ // see AdEvent.AdEventType documentation.
+ switch (adEvent.getType()) {
+ case LOADED:
+ // AdEventType.LOADED is fired when ads are ready to play.
+
+ // This sample app uses the sample tag single_preroll_skippable_ad_tag_url
+ // that requires calling AdsManager.start() to start ad playback. If you use
+ // a different ad tag URL that returns a VMAP or an ad rules playlist,
+ // the adsManager.init() function will trigger ad playback automatically and
+ // the IMA SDK will ignore the adsManager.start(). It is safe to always call
+ // adsManager.start() in the LOADED event.
+ adsManager.start();
+ break;
+ case CONTENT_PAUSE_REQUESTED:
+ // AdEventType.CONTENT_PAUSE_REQUESTED is fired when you
+ // should pause your content and start playing an ad.
+ pauseContentForAds();
+ break;
+ case CONTENT_RESUME_REQUESTED:
+ // AdEventType.CONTENT_RESUME_REQUESTED is fired when the ad
+ // you should play your content.
+ resumeContent();
+ break;
+ case ALL_ADS_COMPLETED:
+ // Calling adsManager.destroy() triggers the function
+ // VideoAdPlayer.release().
+ adsManager.destroy();
+ adsManager = null;
+ break;
+ case CLICKED:
+ // When the user clicks on the Learn More button, the IMA SDK fires
+ // this event, pauses the ad, and opens the ad's click-through URL.
+ // When the user returns to the app, the IMA SDK calls the
+ // VideoAdPlayer.playAd() function automatically.
+ break;
+ default:
+ break;
+ }
+ }
+ });
+ AdsRenderingSettings adsRenderingSettings =
+ ImaSdkFactory.getInstance().createAdsRenderingSettings();
+ adsManager.init(adsRenderingSettings);
+ }
+ });
+
+ // When the play button is clicked, request ads and hide the button.
+ playButton = findViewById(R.id.playButton);
+ playButton.setOnClickListener(
+ view -> {
+ videoPlayer.setVideoPath(SAMPLE_VIDEO_URL);
+ requestAds(SAMPLE_VAST_TAG_URL);
+ view.setVisibility(View.GONE);
+ });
+ }
+
+ private void loadUID2Identity() {
+ InputStream is = getResources().openRawResource(R.raw.uid2identity);
+ StringBuilder text = new StringBuilder();
+
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ text.append(line);
+ text.append('\n');
+ }
+
+ String jsonString = text.toString();
+ JSONObject jsonObject = new JSONObject(jsonString);
+ UID2Identity fromJsonIdentity = UID2Identity.Companion.fromJson(jsonObject);
+
+ // Emulate A UID2Identity With Valid Times
+ long now = System.currentTimeMillis();
+ long identityExpires = now * 60 * 60;
+ long refreshFrom = now * 60 * 40;
+ long refreshExpires = now * 60 * 80;
+
+ UID2Identity identity = new UID2Identity(fromJsonIdentity.getAdvertisingToken(),
+ fromJsonIdentity.getRefreshToken(),
+ identityExpires,
+ refreshFrom,
+ refreshExpires,
+ fromJsonIdentity.getRefreshResponseKey());
+
+ UID2Manager.getInstance().setIdentity(identity);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Error loading Identity: " + e);
+ }
+ }
+
+ private void pauseContentForAds() {
+ Log.i(LOGTAG, "pauseContentForAds");
+ savedPosition = videoPlayer.getCurrentPosition();
+ videoPlayer.stopPlayback();
+ // Hide the buttons and seek bar controlling the video view.
+ videoPlayer.setMediaController(null);
+ }
+
+ private void resumeContent() {
+ Log.i(LOGTAG, "resumeContent");
+
+ // Show the buttons and seek bar controlling the video view.
+ videoPlayer.setVideoPath(SAMPLE_VIDEO_URL);
+ videoPlayer.setMediaController(mediaController);
+ videoPlayer.setOnPreparedListener(
+ mediaPlayer -> {
+ if (savedPosition > 0) {
+ mediaPlayer.seekTo(savedPosition);
+ }
+ mediaPlayer.start();
+ });
+ videoPlayer.setOnCompletionListener(
+ mediaPlayer -> videoAdPlayerAdapter.notifyImaOnContentCompleted());
+ }
+
+ private void requestAds(String adTagUrl) {
+ // Create the ads request.
+ AdsRequest request = sdkFactory.createAdsRequest();
+ request.setAdTagUrl(adTagUrl);
+ request.setContentProgressProvider(
+ () -> {
+ if (videoPlayer.getDuration() <= 0) {
+ return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
+ }
+ return new VideoProgressUpdate(
+ videoPlayer.getCurrentPosition(), videoPlayer.getDuration());
+ });
+
+ // Request the ad. After the ad is loaded, onAdsManagerLoaded() will be called.
+ adsLoader.requestAds(request);
+ }
+}
diff --git a/securesignals-ima-dev-app/src/main/java/com/uid2/dev/VideoAdPlayerAdapter.java b/securesignals-ima-dev-app/src/main/java/com/uid2/dev/VideoAdPlayerAdapter.java
new file mode 100644
index 0000000..353474d
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/java/com/uid2/dev/VideoAdPlayerAdapter.java
@@ -0,0 +1,186 @@
+package com.uid2.dev;
+
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.VideoView;
+
+import com.google.ads.interactivemedia.v3.api.AdPodInfo;
+import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo;
+import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
+import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Example implementation of IMA's VideoAdPlayer interface.
+ * From: VideoAdPlayer
+ */
+public class VideoAdPlayerAdapter implements VideoAdPlayer {
+
+ private static final String LOGTAG = "IMABasicSample";
+ private static final long POLLING_TIME_MS = 250;
+ private static final long INITIAL_DELAY_MS = 250;
+ private final VideoView videoPlayer;
+ private final AudioManager audioManager;
+ private final List videoAdPlayerCallbacks = new ArrayList<>();
+ private Timer timer;
+ private int adDuration;
+
+ // The saved ad position, used to resumed ad playback following an ad click-through.
+ private int savedAdPosition;
+ private AdMediaInfo loadedAdMediaInfo;
+
+ public VideoAdPlayerAdapter(VideoView videoPlayer, AudioManager audioManager) {
+ this.videoPlayer = videoPlayer;
+ this.videoPlayer.setOnCompletionListener(
+ (MediaPlayer mediaPlayer) -> notifyImaOnContentCompleted());
+ this.audioManager = audioManager;
+ }
+
+ @Override
+ public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
+ videoAdPlayerCallbacks.add(videoAdPlayerCallback);
+ }
+
+ @Override
+ public void loadAd(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) {
+ // This simple ad loading logic works because preloading is disabled. To support preloading ads your app must
+ // maintain state for the currently playing ad while handling upcoming ad downloading and buffering at the
+ // same time. See the IMA Android preloading guide for more info:
+ // https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/preload
+ loadedAdMediaInfo = adMediaInfo;
+ }
+
+ @Override
+ public void pauseAd(AdMediaInfo adMediaInfo) {
+ Log.i(LOGTAG, "pauseAd");
+ savedAdPosition = videoPlayer.getCurrentPosition();
+ stopAdTracking();
+ }
+
+ @Override
+ public void playAd(AdMediaInfo adMediaInfo) {
+ videoPlayer.setVideoURI(Uri.parse(adMediaInfo.getUrl()));
+
+ videoPlayer.setOnPreparedListener(
+ mediaPlayer -> {
+ adDuration = mediaPlayer.getDuration();
+ if (savedAdPosition > 0) {
+ mediaPlayer.seekTo(savedAdPosition);
+ }
+ mediaPlayer.start();
+ startAdTracking();
+ });
+ videoPlayer.setOnErrorListener(
+ (mediaPlayer, errorType, extra) -> notifyImaSdkAboutAdError(errorType));
+ videoPlayer.setOnCompletionListener(
+ mediaPlayer -> {
+ savedAdPosition = 0;
+ notifyImaSdkAboutAdEnded();
+ });
+ }
+
+ @Override
+ public void release() {
+ // any clean up that needs to be done.
+ }
+
+ @Override
+ public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
+ videoAdPlayerCallbacks.remove(videoAdPlayerCallback);
+ }
+
+ @Override
+ public void stopAd(AdMediaInfo adMediaInfo) {
+ Log.i(LOGTAG, "stopAd");
+ stopAdTracking();
+ }
+
+ /** Returns current volume as a percent of max volume. */
+ @Override
+ public int getVolume() {
+ return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
+ / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ }
+
+ private void startAdTracking() {
+ Log.i(LOGTAG, "startAdTracking");
+ if (timer != null) {
+ return;
+ }
+ timer = new Timer();
+ TimerTask updateTimerTask =
+ new TimerTask() {
+ @Override
+ public void run() {
+ VideoProgressUpdate progressUpdate = getAdProgress();
+ notifyImaSdkAboutAdProgress(progressUpdate);
+ }
+ };
+ timer.schedule(updateTimerTask, POLLING_TIME_MS, INITIAL_DELAY_MS);
+ }
+
+ private void notifyImaSdkAboutAdEnded() {
+ Log.i(LOGTAG, "notifyImaSdkAboutAdEnded");
+ savedAdPosition = 0;
+ for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) {
+ callback.onEnded(loadedAdMediaInfo);
+ }
+ }
+
+ private void notifyImaSdkAboutAdProgress(VideoProgressUpdate adProgress) {
+ for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) {
+ callback.onAdProgress(loadedAdMediaInfo, adProgress);
+ }
+ }
+
+ /**
+ * @param errorType Media player's error type as defined at
+ * Media Player
+ * @return True to stop the current ad playback.
+ */
+ private boolean notifyImaSdkAboutAdError(int errorType) {
+ Log.i(LOGTAG, "notifyImaSdkAboutAdError");
+
+ switch (errorType) {
+ case MediaPlayer.MEDIA_ERROR_UNSUPPORTED:
+ Log.e(LOGTAG, "notifyImaSdkAboutAdError: MEDIA_ERROR_UNSUPPORTED");
+ break;
+ case MediaPlayer.MEDIA_ERROR_TIMED_OUT:
+ Log.e(LOGTAG, "notifyImaSdkAboutAdError: MEDIA_ERROR_TIMED_OUT");
+ break;
+ default:
+ break;
+ }
+ for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) {
+ callback.onError(loadedAdMediaInfo);
+ }
+ return true;
+ }
+
+ public void notifyImaOnContentCompleted() {
+ Log.i(LOGTAG, "notifyImaOnContentCompleted");
+ for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) {
+ callback.onContentComplete();
+ }
+ }
+
+ private void stopAdTracking() {
+ Log.i(LOGTAG, "stopAdTracking");
+ if (timer != null) {
+ timer.cancel();
+ timer = null;
+ }
+ }
+
+ @Override
+ public VideoProgressUpdate getAdProgress() {
+ long adPosition = videoPlayer.getCurrentPosition();
+ return new VideoProgressUpdate(adPosition, adDuration);
+ }
+}
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-hdpi/ic_action_play_over_video.png b/securesignals-ima-dev-app/src/main/res/drawable-hdpi/ic_action_play_over_video.png
new file mode 100644
index 0000000..157f6b2
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-hdpi/ic_action_play_over_video.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-hdpi/ic_launcher.png b/securesignals-ima-dev-app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..72ebaad
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-mdpi/ic_action_play_over_video.png b/securesignals-ima-dev-app/src/main/res/drawable-mdpi/ic_action_play_over_video.png
new file mode 100644
index 0000000..009abfb
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-mdpi/ic_action_play_over_video.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-mdpi/ic_launcher.png b/securesignals-ima-dev-app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..5ad8cb4
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-xhdpi/ic_action_play_over_video.png b/securesignals-ima-dev-app/src/main/res/drawable-xhdpi/ic_action_play_over_video.png
new file mode 100644
index 0000000..47d09cb
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-xhdpi/ic_action_play_over_video.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-xhdpi/ic_launcher.png b/securesignals-ima-dev-app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..a734148
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/drawable-xxhdpi/ic_launcher.png b/securesignals-ima-dev-app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..585e71d
Binary files /dev/null and b/securesignals-ima-dev-app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/securesignals-ima-dev-app/src/main/res/layout/main_activity.xml b/securesignals-ima-dev-app/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..8f9de08
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/securesignals-ima-dev-app/src/main/res/raw/uid2identity.json b/securesignals-ima-dev-app/src/main/res/raw/uid2identity.json
new file mode 100644
index 0000000..27e9e12
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/res/raw/uid2identity.json
@@ -0,0 +1,9 @@
+{
+ "advertising_token": "NewAdvertisingTokenIjb6u6KcMAtd0/4ZIAYkXvFrMdlZVqfb9LNf99B+1ysE/lBzYVt64pxYxjobJMGbh5q/HsKY7KC0Xo5Rb/Vo8HC4dYOoWXyuGUaL7Jmbw4bzh+3pgokelUGyTX19DfArTeIg7n+8cxWQ=",
+ "refresh_token": "NewRefreshTokenAAAF2c8H5dF8AAAF2c8H5dF8AAAADX393Vw94afoVLL6A+qjdSUEisEKx6t42fLgN+2dmTgUavagz0Q6Kp7ghM989hKhZDyAGjHyuAAwm+CX1cO7DWEtMeNUA9vkWDjcIc8yeDZ+jmBtEaw07x/cxoul6fpv2PQ==",
+ "identity_expires": 1633643601000,
+ "refresh_from": 1633643001000,
+ "refresh_expires": 1636322000000,
+ "refresh_response_key": "yptCUTBoZm1ffosgCrmuwg==",
+ "status": "success"
+}
diff --git a/securesignals-ima-dev-app/src/main/res/values/dimens.xml b/securesignals-ima-dev-app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..edd790e
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/res/values/dimens.xml
@@ -0,0 +1,4 @@
+
+
+ 16sp
+
diff --git a/securesignals-ima-dev-app/src/main/res/values/strings.xml b/securesignals-ima-dev-app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c813800
--- /dev/null
+++ b/securesignals-ima-dev-app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+
+ UID2 IMA Dev App
+ Play button
+
+
diff --git a/securesignals-ima/build.gradle b/securesignals-ima/build.gradle
index 73af1dc..651f834 100644
--- a/securesignals-ima/build.gradle
+++ b/securesignals-ima/build.gradle
@@ -23,10 +23,10 @@ android {
dependencies {
implementation project(path: ':sdk')
- implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.30.0'
+ implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0'
testImplementation 'junit:junit:4.13.2'
- testImplementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.30.0'
+ testImplementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.29.0'
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/settings.gradle b/settings.gradle
index 6bdc9cb..bddb02b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,3 +20,4 @@ include ':dev-app'
include ':sdk'
include ':securesignals-ima'
include ':securesignals-gma'
+include ':securesignals-ima-dev-app'