Skip to content

Commit

Permalink
Secure Signals IMA DevApp (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
bleege authored Apr 19, 2023
1 parent ccaa4f5 commit 2e5d691
Show file tree
Hide file tree
Showing 20 changed files with 627 additions and 2 deletions.
1 change: 1 addition & 0 deletions sdk/src/main/java/com/uid2/data/UID2Identity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions securesignals-ima-dev-app/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
4 changes: 4 additions & 0 deletions securesignals-ima-dev-app/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (7.4.2)" variant="all" version="7.4.2">

</issues>
24 changes: 24 additions & 0 deletions securesignals-ima-dev-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:name=".IMADevApplication"
android:label="@string/app_name"
android:allowBackup="true"
android:icon="@drawable/ic_launcher">
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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());
}

}
277 changes: 277 additions & 0 deletions securesignals-ima-dev-app/src/main/java/com/uid2/dev/MainActivity.java
Original file line number Diff line number Diff line change
@@ -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
* <a href="https://github.com/googleads/googleads-ima-android/tree/main/basicexample">BasicExample</a>
*/
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
* <a href="https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags">Client Side Tags</a>
*/
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);
}
}
Loading

0 comments on commit 2e5d691

Please sign in to comment.