package com.github.andlyticsproject.admob;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;
import com.github.andlyticsproject.AndlyticsApp;
import com.github.andlyticsproject.ContentAdapter;
import com.github.andlyticsproject.Preferences.Timeframe;
import com.github.andlyticsproject.console.NetworkException;
import com.github.andlyticsproject.model.AdmobStats;
public class AdmobRequest {
private static final String TAG = AdmobRequest.class.getSimpleName();
public static final String ERROR_TOKEN_INVALID = "token_invalid";
public static final String ERROR_INVALID_SITE_ID = "site_id_invalid";
public static final String ERROR_RATE_LIMIT_EXCEEDED = "rate_limit_exceeded";
public static final String ERROR_NETWORK_ERROR = "andlytics_network_error";
public static final String ERROR_ACCOUNT_REMOVED = "andlytics_account_removed";
public static final String ERROR_ASK_USER_PASSWORD = "andlytics_account_ask_for_password";
public static final String ERROR_REQUEST_INVALID = "request_invalid";
public static final String KEY_SITE_ID = "site_id";
public static final String KEY_REQUESTS = "requests";
public static final String KEY_HOUSEAD_REQUESTS = "housead_requests";
public static final String KEY_INTERSTITIAL_REQUESTS = "interstitial_requests";
public static final String KEY_IMPRESSIONS = "impressions";
public static final String KEY_FILL_RATE = "fill_rate";
public static final String KEY_HOUSEAD_FILL_RATE = "housead_fill_rate";
public static final String KEY_OVERALL_FILL_RATE = "overall_fill_rate";
public static final String KEY_CLICKS = "clicks";
public static final String KEY_HOUSEAD_CLICKS = "housead_clicks";
public static final String KEY_CTR = "ctr";
public static final String KEY_ECPM = "ecpm";
public static final String KEY_REVENUE = "revenue";
public static final String KEY_CPC_REVENUE = "cpc_revenue";
public static final String KEY_CPM_REVENUE = "cpm_revenue";
public static final String KEY_EXCHANGE_DOWNLOADS = "exchange_downloads";
public static final String KEY_DATE = "date";
@SuppressLint("SimpleDateFormat")
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private static final boolean DEBUG = false;
//base access url to admob api
private static final String BASE_URL = "https://api.admob.com/v2/";
private static final String clientKey = "k1541c79203abfb693edb05ede83891b";
public static final long RETRY_TIME = 4000;
//construct
public AdmobRequest() {
}
//user login
public static String login(String email, String password) throws NetworkException,
AdmobInvalidTokenException, AdmobGenericException, AdmobInvalidRequestException,
AdmobRateLimitExceededException {
String token = null;
//create the parameters
@SuppressWarnings("deprecation")
String params = "client_key=" + clientKey + "&email=" + URLEncoder.encode(email)
+ "&password=" + URLEncoder.encode(password);
try {
//try to login
if (DEBUG)
Log.d(TAG, email + " admob login request");
JSONArray data = getResponse("auth", "login", params, true, true);
//decode the json response and assign token for future use in this session
token = data.getJSONObject(0).getString("token");
} catch (JSONException e) {
throw new AdmobGenericException(e);
}
return token;
}
private static void handleNonJsonException(Exception e, String sb) throws NetworkException,
AdmobGenericException, AdmobInvalidTokenException {
if (e instanceof SocketException || e instanceof UnknownHostException
|| e instanceof IOException || e instanceof NetworkException) {
throw new NetworkException(e);
} else if (e instanceof AdmobInvalidTokenException) {
throw (AdmobInvalidTokenException) e;
} else {
throw new AdmobGenericException(e, sb);
}
}
public static JSONArray getData(Context context, String account, String token, String object,
String method, String[] requestData) throws AdmobAccountRemovedException,
NetworkException, AdmobRateLimitExceededException, AdmobInvalidTokenException,
AdmobGenericException, AdmobAskForPasswordException, AdmobInvalidRequestException {
String params = createRequestParams(token, requestData);
//pass everything to getResponse and return it
JSONArray result = null;
try {
result = getResponse(object, method, params, false, true);
} catch (AdmobInvalidTokenException e) {
// token invalid, maybe too old, retry with new token
invalidateAdmobToken(account, token, context);
String newToken = authenticateAdmobAccount(account, context);
params = createRequestParams(newToken, requestData);
result = getResponse(object, method, params, false, true);
}
return result;
}
private static String createRequestParams(String token, String[] requestData) {
//init params
String params = "client_key=" + clientKey;
params += "&token=" + token;
//build the params for getResponse to use
for (String row : requestData) {
params = params + "&" + row;
}
return params;
}
//private method to handle data requests
private static JSONArray getResponse(String object, String method, String urlParameters,
boolean sendPost, boolean retry) throws NetworkException,
AdmobRateLimitExceededException, AdmobInvalidTokenException, AdmobGenericException,
AdmobInvalidRequestException {
int responseCode = -1;
JSONObject result = null;
String sb = null;
try {
//create the sub url
String subUrl = BASE_URL + object + "/" + method;
HttpsURLConnection con = null;
//check if it needs to be post or get
if (sendPost) {
con = (HttpsURLConnection) new URL(subUrl).openConnection();
con.setHostnameVerifier(new BrowserCompatHostnameVerifier());
// create a connection
con.setDoOutput(true);// allow for posting data to the connection
// pass data to the connection (POST)
OutputStream post = con.getOutputStream();
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(post));
out.write(urlParameters);
out.close();
} else {
if (DEBUG)
Log.d(TAG, subUrl);
con = (HttpsURLConnection) new URL(subUrl + "?" + urlParameters).openConnection();
con.setHostnameVerifier(new BrowserCompatHostnameVerifier());
//System.out.println("admob request: " + subUrl + "?" + urlParameters);
//open the link and post everything to it
}
//read the response
InputStream resultStream = con.getInputStream();
BufferedReader aReader = new java.io.BufferedReader(new java.io.InputStreamReader(
resultStream));
StringBuffer aResponse = new StringBuffer();
String aLine = aReader.readLine();
while (aLine != null) {
aResponse.append(aLine + "\n");
aLine = aReader.readLine();
}
resultStream.close();
sb = aResponse.toString();
if (con instanceof HttpURLConnection) {
HttpURLConnection httpConnection = (HttpURLConnection) con;
responseCode = httpConnection.getResponseCode();
}
if (sb == null || "".equals(sb.trim())) {
Log.w(TAG, "admob empty respsonse" + " token invalid?");
throw new AdmobInvalidTokenException(responseCode + " null response");
} else if (!sb.startsWith("{")) {
throw new AdmobInvalidTokenException(responseCode + " invalid response: " + sb);
}
result = new JSONObject(sb);
// } catch (IOException e) {
// throw new NetworkException(e);
} catch (JSONException e) {
handleNonJsonException(e, responseCode + " " + sb);
} catch (Exception e) {
handleNonJsonException(e, responseCode + " " + sb);
}
// check json for errors
try {
checkJsonErrors(result);
} catch (AdmobRateLimitExceededException e) {
if (retry) {
Log.w(TAG, "admob rateLimitExceeded, retry in 5 sec...");
try {
Thread.sleep(AdmobRequest.RETRY_TIME);
} catch (InterruptedException e1) {
}
return getResponse(object, method, urlParameters, sendPost, false);
} else {
throw e;
}
}
JSONArray dataObject = toJsonArray(result, "data");
return dataObject;
}
private static void checkJsonErrors(JSONObject result) throws AdmobInvalidTokenException,
AdmobRateLimitExceededException, AdmobGenericException, AdmobInvalidRequestException {
Map<String, String> errorMap = new HashMap<String, String>();
JSONArray errors = (result).optJSONArray("errors");
if (errors != null && errors.length() > 0) {
for (int i = 0; i < errors.length(); i++) {
try {
String code = errors.getJSONObject(i).optString("code");
String message = errors.getJSONObject(i).optString("msg");
errorMap.put(code, message);
} catch (JSONException e) {
throw new AdmobGenericException(e);
}
}
}
if (errorMap.size() > 0) {
if (errorMap.containsKey(ERROR_RATE_LIMIT_EXCEEDED)) {
String exMessage = ERROR_RATE_LIMIT_EXCEEDED + " "
+ errorMap.get(ERROR_RATE_LIMIT_EXCEEDED);
throw new AdmobRateLimitExceededException(exMessage);
} else if (errorMap.containsKey(ERROR_REQUEST_INVALID)) {
String exMessage = ERROR_REQUEST_INVALID + " "
+ errorMap.get(ERROR_REQUEST_INVALID);
throw new AdmobInvalidRequestException(exMessage);
} else if (errorMap.containsKey(ERROR_TOKEN_INVALID)) {
String exMessage = ERROR_TOKEN_INVALID + " " + errorMap.get(ERROR_TOKEN_INVALID);
throw new AdmobInvalidTokenException(exMessage);
} else {
String code = errorMap.keySet().iterator().next();
throw new AdmobGenericException(code + " " + errorMap.get(code));
}
}
}
private static JSONArray toJsonArray(JSONObject result, String elementName) {
JSONArray dataObject = (result).optJSONArray(elementName);
if (dataObject == null) {
dataObject = new JSONArray();
dataObject.put(result.optJSONObject(elementName));
}
return dataObject;
}
public static void syncSiteStats(String account, Context context, List<String> siteList,
SyncCallback callback) throws AdmobRateLimitExceededException,
AdmobAccountRemovedException, NetworkException, AdmobInvalidTokenException,
AdmobGenericException, AdmobAskForPasswordException, AdmobInvalidRequestException {
if (DEBUG)
Log.d(TAG, "admob site sync request for " + siteList.size() + " sites");
String token = authenticateAdmobAccount(account, context);
for (int k = 0; k < siteList.size(); k++) {
String admobSiteId = siteList.get(k);
boolean bulkInsert = false;
Date startDate = null;
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, 1);
Date endDate = calendar.getTime();
// read db for required sync period
ContentAdapter contentAdapter = ContentAdapter.getInstance(AndlyticsApp.getInstance());
List<AdmobStats> admobStats = contentAdapter.getAdmobStats(admobSiteId,
Timeframe.LATEST_VALUE).getStats();
if (admobStats.size() > 0) {
// found previouse sync, no bulk import
startDate = admobStats.get(0).getDate();
Calendar startCal = Calendar.getInstance();
startCal.setTime(startDate);
startCal.add(Calendar.DAY_OF_YEAR, -4);
startDate = startCal.getTime();
} else {
calendar.add(Calendar.MONTH, -6);
startDate = calendar.getTime();
bulkInsert = true;
}
List<AdmobStats> result = new ArrayList<AdmobStats>();
//String endDate = dateFormat.format(calendar.getTime());
JSONArray data = getData(context, account, token, "site", "stats", new String[] {
"site_id=" + admobSiteId, "end_date=" + dateFormat.format(endDate),
"start_date=" + dateFormat.format(startDate), "time_dimension=day",
"order_by[date]=desc" });
if (callback != null && bulkInsert) {
callback.initialImportStarted();
}
//go through the returned array
for (int i = 0; i < data.length(); i++) {
try {
//convert the line of data to a json object so we can pick specific data out of it
JSONObject adObject = new JSONObject(data.get(i).toString());
AdmobStats admob = new AdmobStats();
admob.setSiteId(admobSiteId);
admob.setClicks(adObject.getInt(KEY_CLICKS));
admob.setCpcRevenue(Float.parseFloat(adObject.getString(KEY_CPC_REVENUE)));
admob.setCpmRevenue(Float.parseFloat(adObject.getString(KEY_CPM_REVENUE)));
admob.setCtr(Float.parseFloat(adObject.getString(KEY_CTR)));
admob.setDate(dateFormat.parse(adObject.getString(KEY_DATE)));
admob.setEcpm(Float.parseFloat(adObject.getString(KEY_ECPM)));
admob.setExchangeDownloads(adObject.getInt(KEY_EXCHANGE_DOWNLOADS));
admob.setFillRate(Float.parseFloat(adObject.getString(KEY_FILL_RATE)));
admob.setHouseAdClicks(adObject.getInt(KEY_HOUSEAD_CLICKS));
admob.setHouseadFillRate(Float.parseFloat(adObject
.getString(KEY_HOUSEAD_FILL_RATE)));
admob.setHouseadRequests(adObject.getInt(KEY_HOUSEAD_REQUESTS));
admob.setImpressions(adObject.getInt(KEY_IMPRESSIONS));
admob.setInterstitialRequests(adObject.getInt(KEY_INTERSTITIAL_REQUESTS));
admob.setOverallFillRate(Float.parseFloat(adObject
.getString(KEY_OVERALL_FILL_RATE)));
admob.setRequests(adObject.getInt(KEY_REQUESTS));
admob.setRevenue(Float.parseFloat(adObject.getString(KEY_REVENUE)));
result.add(admob);
} catch (Exception e) {
throw new AdmobGenericException(e);
}
}
if (bulkInsert) {
if (result.size() > 6) {
// insert first results single to avoid manual triggered doubles
List<AdmobStats> subList1 = result.subList(0, 5);
for (AdmobStats admob : subList1) {
contentAdapter.insertOrUpdateAdmobStats(admob);
}
List<AdmobStats> subList2 = result.subList(5, result.size());
contentAdapter.bulkInsertAdmobStats(subList2);
} else {
contentAdapter.bulkInsertAdmobStats(result);
}
} else {
for (AdmobStats admob : result) {
contentAdapter.insertOrUpdateAdmobStats(admob);
}
}
if (k != siteList.size() - 1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Log.d(TAG, "wait to avoid rate limit exception");
}
}
}
private static String authenticateAdmobAccount(String currentAdmobAccount, Context context)
throws AdmobRateLimitExceededException, AdmobInvalidTokenException,
AdmobAccountRemovedException, AdmobAskForPasswordException,
AdmobInvalidRequestException {
String admobToken = AdmobAuthenticationUtilities.authenticateAccount(currentAdmobAccount,
context);
if (AdmobRequest.ERROR_RATE_LIMIT_EXCEEDED.equals(admobToken)) {
throw new AdmobRateLimitExceededException(admobToken);
} else if (AdmobRequest.ERROR_REQUEST_INVALID.equals(admobToken)) {
throw new AdmobInvalidRequestException(admobToken);
} else if (AdmobRequest.ERROR_ACCOUNT_REMOVED.equals(admobToken)) {
throw new AdmobAccountRemovedException(admobToken, currentAdmobAccount);
} else if (AdmobRequest.ERROR_ASK_USER_PASSWORD.equals(admobToken)) {
throw new AdmobAskForPasswordException("Missing password");
}
return admobToken;
}
private static void invalidateAdmobToken(String accountName, String token, Context context) {
if (DEBUG)
Log.d(TAG, "invalidate admob token for " + accountName);
AdmobAuthenticationUtilities.invalidateToken(token, context);
}
public interface SyncCallback {
void initialImportStarted();
}
public static Map<String, String> getSiteList(String account, Context context)
throws AdmobRateLimitExceededException, AdmobInvalidTokenException,
AdmobAccountRemovedException, NetworkException, AdmobGenericException,
AdmobAskForPasswordException, AdmobInvalidRequestException {
Map<String, String> result = new HashMap<String, String>();
String token = authenticateAdmobAccount(account, context);
JSONArray data = AdmobRequest.getData(context, account, token, "site", "search",
new String[] {});
//go through the returned array
for (int i = 0; i < data.length(); i++) {
//convert the line of data to a json object so we can pick specific data out of it
JSONObject adObject;
try {
adObject = new JSONObject(data.get(i).toString());
String id = adObject.getString("id");
String name = adObject.getString("name");
result.put(id, name);
} catch (JSONException e) {
throw new AdmobGenericException(e);
}
}
return result;
}
}