Integration guide - adcharge/sdk-android-demo GitHub Wiki
Integrated AdCharge SDK provides possibility to show ads upon incoming call event or/and in the application directly. With active ads upon incoming call User will see small banner, which is a preview of a large one. User can interact with the small banner by closing it or sliding it up and down. After call has finished User will see large, full-screen banner. User is able to interact, within 10 seconds with it by closing, hiding or clicking on it. After 10 seconds, if no action was done, banner will be automatically closed. If banner was clicked User will be redirected to default mobile device browser for further actions. Active in application ads option gives possibility to show full screen banner directly in application in a flexible way and open application browser in case of a click (if such exists in the applications).
For integration with AdCharge SDK you will need:
- 2 .aar files
- server base URL (will be provided by AdCharge manager)
- individual key of traffic source (will be provided by AdCharge manager)
- your application
- provided by AdCharge, ask your AdCharge manager
- provided by AdCharge, ask your AdCharge manager
- provided by AdCharge, ask your AdCharge manager
- application must have minimum API level 19 or above (Android 4.4 KitKat) – <app_root_directory>/app/build.gradle minSdkVersion
- application must be ready to request user about next permissions: – display over other apps – manage phone calls – access device location
-
add aar files to your project as shown in [https://developer.android.com/studio/projects/android-library.html#AddDependency]
-
add dependencies in your <app_root_directory>/app/build.gradle
dependencies {
//.....yours project dependencies
// >>> start of AdCharge dependencies
// dependencies for AdCharge API wrapper
implementation project(':adcharge_api_sdk-release')
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.8.1'
implementation group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.10.0'
implementation "com.squareup.okhttp3:okhttp-urlconnection:3.0.0-RC1"
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.5'
// dependencies for AdCharge SDK
implementation project(":adcharge_sdk-release")
implementation 'com.google.android.gms:play-services-location:11.0.4'
implementation 'com.android.support:appcompat-v7:27.1.1'
// <<< end of AdCharge dependencies
}
- make sure adcharge_api_sdk-release and adcharge_sdk-release are named same to .aar files in lines
implementation project(':adcharge_api_sdk-release')
implementation project(":adcharge_sdk-release")
- add dependencies in your <app_root_directory>/app/build.gradle
android {
//.....yours project configs
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
- pick an action in your app, which will initialize AdCharge. For example, switcher
xml:
<Switch
android:id="@+id/use_adcharge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show me advertisement" />
java:
protected void onCreate(Bundle savedInstanceState) {
.....
Switch useAdcharge = findViewById(R.id.use_adcharge);
.....
}
- create new class to place there integration code
java:
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.provider.Settings;
import android.widget.CompoundButton;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import eu.adcharge.api.ApiException;
import eu.adcharge.api.ApiValidationException;
import eu.adcharge.api.entities.User;
import eu.adcharge.sdk.logic.AdCharge;
class AdChargeDependentCode {
private static final String URL = "https://adm.adcharge.eu/api/"; // Server base URL
private static final AdCharge.Settings SETTINGS = new AdCharge.Settings(); // default settings
private static final String TRAFFIC_SOURCE_KEY = "Speedflow-AdCharge"; // Individual key of traffic source
private static List<String> getAdchargeCredentailsForUniqueUser(Context ctx) {
List<String> credentials = new ArrayList<>();
String androidId = Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID);
credentials.add(androidId);
credentials.add("verySecureUserPassword");
return credentials;
}
static CompoundButton.OnCheckedChangeListener LISTENER;
static AdCharge initAdcharge(final Activity activity) throws MalformedURLException {
final AdCharge adCharge = new AdCharge(URL, activity, SETTINGS);
LISTENER = new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
List<String> credentials = getAdchargeCredentailsForUniqueUser(activity);
final String username = credentials.get(0);
final String password = credentials.get(1);
new LoginUserTask(username, password, activity, adCharge).execute();
} else {
new LogoutUserTask(adCharge).execute();
}
}
};
return adCharge;
}
static class LoginUserTask extends AsyncTask<Object, Object, Object> {
private String username;
private String password;
private Activity activity;
private AdCharge adCharge;
LoginUserTask(String username, String password, Activity activity, AdCharge adCharge) {
this.username = username;
this.password = password;
this.activity = activity;
this.adCharge = adCharge;
}
@Override
protected Object doInBackground(Object[] objects) {
try {
adCharge.login(username, password, TRAFFIC_SOURCE_KEY, activity);
} catch (ApiException | IOException | ApiValidationException e) {
User toBeRegistered = new User();
toBeRegistered.setUsername(username);
toBeRegistered.setPassword(password);
try {
adCharge.registerSubscriberUser(toBeRegistered, TRAFFIC_SOURCE_KEY);
adCharge.login(username, password, TRAFFIC_SOURCE_KEY, activity);
} catch (ApiException | IOException | ApiValidationException e1) {
e1.printStackTrace();
}
}
return null;
}
}
static class LogoutUserTask extends AsyncTask<Object, Object, Object> {
private AdCharge adCharge;
LogoutUserTask(AdCharge adCharge) {
this.adCharge = adCharge;
}
@Override
protected Object doInBackground(Object[] objects) {
adCharge.logout();
return null;
}
}
}
- connect your switcher to AdCharge java:
Switch useAdcharge = findViewById(R.id.use_adcharge);
try {
AdCharge adcharge = AdChargeDependentCode.initAdcharge(this);
useAdcharge.setOnCheckedChangeListener(AdChargeDependentCode.LISTENER);
useAdcharge.setChecked(adcharge.isLoggedIn());
} catch (MalformedURLException ignored) {
useAdcharge.setEnabled(false);
}
Now if your switcher is on you should see advertisement upon incoming call
-
add in AdChargeDependentCode AsyncTaskWithTimeout class
java:
class AdChargeDependentCode {
...
abstract static class AsyncTaskWithTimeout<Params, Progress, Result>
extends AsyncTask<Params, Progress, Result> {
private final long timeout;
private final TimeUnit units;
private final Activity context;
// used for interruption
private Thread backgroundThread;
public AsyncTaskWithTimeout(Activity context, long timeout, TimeUnit units) {
this.context = context;
this.timeout = timeout;
this.units = units;
}
@Override
protected final void onPreExecute() {
Thread timeoutThread = new Thread(new Runnable() {
@Override
public void run() {
try {
// start the timeout ticker
AsyncTaskWithTimeout.this.get(timeout, units);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
onException(e);
} catch (TimeoutException e) {
AsyncTaskWithTimeout.this.interruptTask();
context.runOnUiThread(new Runnable() {
@Override
public void run() {
onTimeout();
}
});
}
}
});
timeoutThread.setDaemon(true);
onPreExec();
timeoutThread.start();
}
protected void onPreExec() {
}
@Override
protected final Result doInBackground(Params... params) {
// save off reference to background thread so it can be interrupted on timeout
this.backgroundThread = Thread.currentThread();
return runInBackground(params);
}
protected abstract Result runInBackground(Params... params);
protected void onTimeout() {
}
protected void onException(ExecutionException e) {
throw new RuntimeException(e);
}
private final void interruptTask() {
if (backgroundThread != null) {
backgroundThread.interrupt();
}
}
}
}
-
create new activity for full-screen in-app advertisement
By using AsyncTaskWithTimeout it's possible to limit show ad operation with specific timeframe (1 second or less recommended if ad was preloaded beforehand. See next paragraph about preloading ad)
java:
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.support.v7.app.AppCompatActivity;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import eu.adcharge.api.ApiException;
import eu.adcharge.api.ApiValidationException;
import eu.adcharge.api.NoAdvertisementFoundException;
import eu.adcharge.sdk.logic.AdCharge;
import eu.adcharge.sdk.logic.InAppAdvertisement;
public class AdActivity extends AppCompatActivity {
private boolean shown = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final AdCharge adCharge = ((Application) getApplication()).getAdCharge();
new AdChargeDependentCode.AsyncTaskWithTimeout<Void, Void, InAppAdvertisement>(this, 1, TimeUnit.SECONDS) {
@Override
protected InAppAdvertisement runInBackground(Void... objects) {
try {
return adCharge.getInAppAdvertisement();
} catch (NoAdvertisementFoundException e) {
// or just no ads for this user available (normal flow)
// or, if ad was preloaded long time ago - it's already invalidated and new one isn't available
// (there are different reasons for invalidation, and better not to show this ad at all)
} catch (ApiException | ApiValidationException | IOException e) {
e.printStackTrace();
// might be a bug or temporal network issue
// make sense to log and collect data, such cases, if shared, might help us
}
return null;
}
@Override
protected void onPostExecute(final InAppAdvertisement ad) {
if (ad == null)
finish(); // Something went wrong (ad has expired, or wasn't preloaded and no new ads are available)
shown = true;
ImageView adSpace = findViewById(R.id.banner);
ad.onDisplayed(); // Responsible for impression report. Mandatory
adSpace.setImageBitmap(ad.getBanner());
adSpace.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, ad.getTrackingUri());
browserIntent.putExtra("android.support.customtabs.extra.SESSION", getPackageName());
browserIntent.putExtra("android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS", true);
startActivity(browserIntent);
}
});
}
@Override
protected void onTimeout() {
finish(); // In case of any issue for set timeout - operation took too long (1 second by default)
}
}.execute();
setContentView(R.layout.activity_ad);
}
@Override
protected void onResume() {
super.onResume();
if (shown) finish();
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AdActivity">
<ImageView
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter" />
</android.support.constraint.ConstraintLayout>
-
preload of in-app ads
If it's possible to predict moment, when ad supposed to be shown - preload it few seconds before usage, but no more, than 30 seconds, because that exact ad may become obsolete for different reasons with time.
Preload is not required, but improves performance and gives warranty for app there's an advertisement for user.
Preload is recommended , on 'get ad for direct usage'. SDK checks if there's preloaded ad and is it still valid, if not - attempt to get another ad from net will be in place.
By using AsyncTaskWithTimeout it's possible to limit operation in timeframe (5 seconds should be more, than enough, if ad is not loaded in 5 seconds, there's definitely some issues with connection or any other sort of, no point to continue, regular time is ~0.5 or less, depends on connection quality).
java:
new AdChargeDependentCode.AsyncTaskWithTimeout<Void, Void, Void>(MainActivity.this, 5, TimeUnit.SECONDS) {
boolean adPreloaded = false;
@Override
protected Void runInBackground(Void... voids) {
try {
adCharge.preloadInAppAdvertisement();
adPreloaded = true;
} catch (NoAdvertisementFoundException e) {
// just no advertisement for this user available
// (normal flow, happens for different reasons)
} catch (ApiException | ApiValidationException | IOException e) {
e.printStackTrace();
// might be a bug or temporal network issue
// make sense to log and collect data, such cases, if shared might help AdCharge to improve it
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if (adPreloaded) {
Intent adActivity = new Intent(getApplicationContext(), AdActivity.class);
adActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(adActivity);
}
}
}.execute();
- Check if Advertiser exists.
- Check if Advertiser has money on his account. If not contact AdCharge.
- Check if Creative is uploaded
- Check if Campaign is created
- Check if Campaign is verified by Admin
- Check if Campaign is verified by Operator
- Check if created Campaign is supported by Operator
- Check if Campaign start date is not in future
- Check if Campaign is still active
- Check if daily or/and total budget of the Campaign has not exceeded
- Check if User corresponds to audience requirement
- Check if targeted Country of the Campaign corresponds to User location
After successfully created instance of AdCharge class and simple test passed you might need to extend and provide more info about your user, what includes:
- Registration of subscriber
- Verification of subscriber
- Get subscriber profile
- Update profile of subscriber
In the code currently you have Android device ID used as username and password - just hardcoded string. You will need to provide unique username which might be a hashed version of username in your application and generated password. Also, by providing information of user's birthday and gender more precise targeting of advertisement сan be used and more variety of ads can be shown. As a result you will have unverified account, which needs to be confirmed later.
import eu.adcharge.api.entities.User;
import eu.adcharge.api.entities.Gender;
import eu.adcharge.sdk.logic.AdCharge;
static class RegisterUserTask extends AsyncTask {
private AdCharge adCharge;
RegisterUserTask(AdCharge adCharge) {
this.adCharge = adCharge;
}
@Override
protected Object doInBackground(Object[] objects) {
String username = "username";// To be provide unique username connected to this exact user of your app
String password = "password";// To be provide generated password
Date birthday = null;
Gender gender = Gender.UNDEFINED;// or Gender.MALE, or Gender.FEMALE
try {
User toBeRegistered = new User();
toBeRegistered.setUsername(username);
toBeRegistered.setPassword(password);
User notVerifiedUser = adCharge.registerSubscriberUser(toBeRegistered, TRAFFIC_SOURCE_KEY);
} catch (ApiException | IOException | ApiValidationException e) {
// do nothing
}
return null;
}
}
Upon registration subscriber will be checked by sending unique secret code to trusted service (your API) That service can be configured to deliver or not confirmation code for every exact registration request. It could be automated registration (no code needed) You will need to find a way using secured channel to get this code from your API With this code registration of the user can be confirmed.
import eu.adcharge.sdk.logic.AdCharge;
static class ConfirmUserTask extends AsyncTask {
private AdCharge adCharge;
private String confirmationCode;
ConfirmUserTask(AdCharge adCharge, String confirmationCode) {
this.adCharge = adCharge;
this.confirmationCode = confirmationCode;
}
@Override
protected Object doInBackground(Object[] objects) {
try {
adCharge.confirmUser(confirmationCode);
} catch (ApiException | IOException | ApiValidationException e) {
// do nothing
}
return null;
}
}
Then you may login as described in
If you need to get information about logged in subscriber
import eu.adcharge.api.entities.User;
import eu.adcharge.sdk.logic.AdCharge;
static class GetUserInfoTask extends AsyncTask {
private AdCharge adCharge;
GetUserInfoTask(AdCharge adCharge) {
this.adCharge = adCharge;
}
@Override
protected Object doInBackground(Object[] objects) {
try {
User user = adCharge.getApiWrapper().getUserInfo();
} catch (ApiException | IOException | ApiValidationException e) {
// do nothing
}
return null;
}
}
User class contains
- int id;
- String username;
- String operator_name;
- Date birthday;
- Gender gender;
- Date date_joined;
- List interests;
For any case, if you need to update information about subscriber
import eu.adcharge.api.entities.User;
import eu.adcharge.api.entities.Gender;
import eu.adcharge.api.entities.Interest;
import eu.adcharge.sdk.logic.AdCharge;
static class UpdateUserInfoTask extends AsyncTask {
private AdCharge adCharge;
private User user;
UpdateUserInfoTask(AdCharge adCharge, User user) {
this.adCharge = adCharge;
this.user = user;
}
@Override
protected Object doInBackground(Object[] objects) {
try {
// here how you can get List of supported interests
List<Interest> availableInterests = adCharge.getApiWrapper().getAvailableInterests();
for(Interest interest : availableInterests){
if(<user interested in>){
user.getInterests().add(interest);
}
}
user.setBirthday(null);
user.setGender(Gender.UNDEFINED);// or Gender.MALE or Gender.FEMALE
user.setPassword("NEW_PASSWORD");
adCharge.getApiWrapper().saveUser(user); // save user profile
} catch (ApiException | IOException | ApiValidationException e) {
// do nothing
}
return null;
}
}
SDK provides access to user statistics which contains information about amount of views, clicks, conversions, earned bonus points and current bonus points balance.
import eu.adcharge.api.entities.Statistics;
import eu.adcharge.sdk.logic.AdCharge;
static class GetUserStatisticsTask extends AsyncTask {
private AdCharge adCharge;
GetUserStatisticsTask(AdCharge adCharge) {
this.adCharge = adCharge;
}
@Override
protected Object doInBackground(Object[] objects) {
try {
Date now = new Date();
Date past24hours = new Date(now.getTime() - 24 * 60 * 60 * 1000);
Statistics statistics = adCharge.getApiWrapper().getStatistics(past24hours, now);
// Views: statistics.getViews()
// Clicks: statistics.getClicks()
// Actions: statistics.getActions()
// Earned Bonuses: statistics.getBonuses()
//
// Current Balance for all time: statistics.getBalance()
} catch (ApiException | IOException | ApiValidationException e) {
// do nothing
}
return null;
}
}
SDK behavior might be customized with settings. Settings are provided as a parameter in constructor (see AdChargeDependentCode.initAdcharge(..) method)
private static final AdCharge.Settings SETTINGS = new AdCharge.Settings();
final AdCharge adCharge = new AdCharge(URL, activity, SETTINGS);
Default values might be overwritten like (after explanation you will find full setting object to use)
- Do not ask user for location permission (need to be discussed if IP based location targeting allowed for this app as traffic source, ask your manager)
private static final AdCharge.Settings SETTINGS = new AdCharge.Settings().useLocation(false) // or .useLocation(true)
- Enable/disable advertisement upon incoming call
private static final AdCharge.Settings SETTINGS = new AdCharge.Settings().showAdsOnCall(true)// or .showAdsOnCall(false)
- Configuration of small banner upon a call
private static final AdCharge.Settings.SmallBanner SMALL_BANNER_SETTINGS = new AdCharge.Settings.SmallBanner() private static final AdCharge.Settings SETTINGS = new AdCharge.Settings().smallBanner(SMALL_BANNER_SETTINGS)
- Display or not small banner (need to be discussed with AdCharge, if allowed to skip small banner)
private static final AdCharge.Settings.SmallBanner SMALL_BANNER_SETTINGS = new AdCharge.Settings.SmallBanner() .enable(true) // or .enable(false)
- Initial placement of small banner on screen (middle, top or bottom)
private static final AdCharge.Settings.SmallBanner SMALL_BANNER_SETTINGS = new AdCharge.Settings.SmallBanner() .initialPosition(AdCharge.Settings.SmallBanner.InitialPosition.MIDDLE) // or AdCharge.Settings.SmallBanner.InitialPosition.TOP // or AdCharge.Settings.SmallBanner.InitialPosition.BOTTOM
- Small banner draggable vertically and you can select draggable area( whole banner, drag icon or not draggable)
private static final AdCharge.Settings.SmallBanner SMALL_BANNER_SETTINGS = new AdCharge.Settings.SmallBanner() .dragSensitiveArea(AdCharge.Settings.SmallBanner.DragSensitiveArea.HOLE_BANNER) // or .dragSensitiveArea(AdCharge.Settings.SmallBanner.DragSensitiveArea.DRAG_ICON) // or .dragSensitiveArea(AdCharge.Settings.SmallBanner.DragSensitiveArea.NONE)
- There's drag icon on small banner and you may hide it
private static final AdCharge.Settings.SmallBanner SMALL_BANNER_SETTINGS = new AdCharge.Settings.SmallBanner() .dragIconDisplayed(false) // or .dragIconDisplayed(true)
- Display or not small banner (need to be discussed with AdCharge, if allowed to skip small banner)
- Configuration of large banner upon a call
private static final AdCharge.Settings.LargeBanner LARGE_BANNER_SETTINGS = new AdCharge.Settings.LargeBanner() private static final AdCharge.Settings SETTINGS = new AdCharge.Settings().largeBanner(LARGE_BANNER_SETTINGS)
- Display or not current bonus points of user on large banner
private static final AdCharge.Settings.SmallBanner SMALL_BANNER_SETTINGS = new AdCharge.Settings.SmallBanner() .bonusPointsBalanceDisplayed(false) // or .bonusPointsBalanceDisplayed(true)
- Display or not current bonus points of user on large banner
- Notification properties
private static final AdCharge.Settings.NotificationProperties NOTIFICATION_SETTINGS = new AdCharge.Settings.NotificationProperties()
.showBigIcon(true)
// or .showBigIcon(true)
.title("notification title here")
// by default will be '<app name>'
.text("notification title here");
// by default will be 'Tap to open <app name>'
.activityClassToOpen(ClassOfActivityToBeLaunchedOnClick.class)
// by default launcher will be used
- Full settings object
private static AdCharge.Settings ADCHARGE_SETTINGS = new AdCharge.Settings() // # Ask user for location .useLocation(false) // or .useLocation(true) .showAdsOnCall(true) // or .showAdsOnCall(false) // # Configuration of incoming call small banner .smallBanner( new AdCharge.Settings.SmallBanner() // # show or do not show small banner .enable(true) // or .enable(false) // // # initial placement on screen .initialPosition(AdCharge.Settings.SmallBanner.InitialPosition.MIDDLE) // or .initialPosition(AdCharge.Settings.SmallBanner.InitialPosition.TOP) // or .initialPosition(AdCharge.Settings.SmallBanner.InitialPosition.BOTTOM) // // # chose draggable area .dragSensitiveArea(AdCharge.Settings.SmallBanner.DragSensitiveArea.HOLE_BANNER) // or .dragSensitiveArea(AdCharge.Settings.SmallBanner.DragSensitiveArea.DRAG_ICON) // or .dragSensitiveArea(AdCharge.Settings.SmallBanner.DragSensitiveArea.NONE) // // # display or hide 'drag icon' .dragIconDisplayed(false) // or .dragIconDisplayed(true) ) // # Configuration of incoming call large banner .largeBanner( new AdCharge.Settings.LargeBanner() // # show or do not show current user bonus points on large banner .bonusPointsBalanceDisplayed(false) // or .bonusPointsBalanceDisplayed(true) ) .notificationProperties( new AdCharge.Settings.NotificationProperties() .showBigIcon(true) // or .showBigIcon(true) .title("notification title here") // by default will be '<app name>' .text("notification title here") // by default will be 'Tap to open <app name>' .activityClassToOpen(ClassOfActivityToBeLaunchedOnClick.class) // by default launcher will be used );