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'