package com.github.andlyticsproject.adsense;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.github.andlyticsproject.AndlyticsApp;
import com.github.andlyticsproject.ContentAdapter;
import com.github.andlyticsproject.Preferences.Timeframe;
import com.github.andlyticsproject.model.AdmobStats;
import com.github.andlyticsproject.util.Utils;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.adsense.AdSense;
import com.google.api.services.adsense.AdSense.Accounts.Reports.Generate;
import com.google.api.services.adsense.AdSenseScopes;
import com.google.api.services.adsense.model.Account;
import com.google.api.services.adsense.model.Accounts;
import com.google.api.services.adsense.model.AdClients;
import com.google.api.services.adsense.model.AdUnit;
import com.google.api.services.adsense.model.AdUnits;
import com.google.api.services.adsense.model.AdsenseReportsGenerateResponse;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@SuppressLint("SimpleDateFormat")
public class AdSenseClient {
private static final String TAG = AdSenseClient.class.getSimpleName();
private static final boolean DEBUG = false;
private static final DateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
private static final String APPLICATION_NAME = "Andlytics/2.6.0";
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
private static final int MAX_LIST_PAGE_SIZE = 50;
private static final long MILLIES_IN_DAY = 60 * 60 * 24 * 1000L;
private AdSenseClient() {
}
public static void foregroundSyncStats(Context context, String admobAccount,
List<String> adUnits) throws Exception {
try {
AdSense adsense = createForegroundSyncClient(context, admobAccount);
syncStats(context, adsense, adUnits);
} catch (GoogleJsonResponseException e) {
List<ErrorInfo> errors = e.getDetails().getErrors();
for (ErrorInfo err : errors) {
if ("dailyLimitExceeded".equals(err.getReason())) {
// ignore
Log.w(TAG, "Quota exeeded: " + e.toString());
return;
}
}
throw e;
}
}
private static AdSense createForegroundSyncClient(Context context, String admobAccount) {
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context,
Collections.singleton(AdSenseScopes.ADSENSE_READONLY));
credential.setSelectedAccountName(admobAccount);
AdSense adsense = new AdSense.Builder(AndroidHttp.newCompatibleTransport(), JSON_FACTORY,
credential).setApplicationName(APPLICATION_NAME).build();
return adsense;
}
public static void backgroundSyncStats(Context context, String admobAccount,
List<String> adUnits, Bundle extras, String authority, Bundle syncBundle)
throws Exception {
try {
AdSense adsense = createBackgroundSyncClient(context, admobAccount, extras, authority,
syncBundle);
syncStats(context, adsense, adUnits);
} catch (GoogleJsonResponseException e) {
List<ErrorInfo> errors = e.getDetails().getErrors();
for (ErrorInfo err : errors) {
if ("dailyLimitExceeded".equals(err.getReason())) {
// ignore
Log.w(TAG, "Quota exeeded: " + e.toString());
return;
}
}
throw e;
}
}
private static void syncStats(Context context, AdSense adsense, List<String> adUnits)
throws Exception {
Calendar[] syncPeriod = getSyncPeriod(adUnits);
boolean bulkInsert = false;
Date startDate = syncPeriod[0].getTime();
Date endDate = syncPeriod[1].getTime();
if ((endDate.getTime() - startDate.getTime()) > 7 * MILLIES_IN_DAY) {
bulkInsert = true;
}
Account account = getFirstAccount(adsense);
if (account == null) {
return;
}
// we assume there is only one(?)
String adClientId = getClientId(adsense, account);
if (adClientId == null) {
// XXX throw?
return;
}
List<AdmobStats> result = generateReport(adsense, account, adClientId, startDate, endDate);
updateStats(context, bulkInsert, result);
}
private static Calendar[] getSyncPeriod(List<String> adUnits) {
Calendar startDateCal = null;
Calendar endDateCal = Calendar.getInstance();
endDateCal.add(Calendar.DAY_OF_YEAR, 1);
for (String adUnit : adUnits) {
// read db for required sync period
ContentAdapter contentAdapter = ContentAdapter.getInstance(AndlyticsApp.getInstance());
List<AdmobStats> admobStats = contentAdapter.getAdmobStats(adUnit,
Timeframe.LATEST_VALUE).getStats();
if (admobStats.size() > 0) {
// found previous sync, no bulk import
Date startDate = admobStats.get(0).getDate();
startDateCal = Calendar.getInstance();
startDateCal.setTime(startDate);
startDateCal.add(Calendar.DAY_OF_YEAR, -4);
} else {
startDateCal = Calendar.getInstance();
startDateCal.setTime(endDateCal.getTime());
startDateCal.add(Calendar.MONTH, -6);
}
}
return new Calendar[] { startDateCal, endDateCal };
}
private static String getClientId(AdSense adsense, Account account) throws IOException {
AdClients adClients = adsense.accounts().adclients().list(account.getId())
.setMaxResults(MAX_LIST_PAGE_SIZE).setPageToken(null).execute();
if (adClients.getItems() == null || adClients.getItems().isEmpty()) {
return null;
}
// we assume there is only one(?)
return adClients.getItems().get(0).getId();
}
private static Account getFirstAccount(AdSense adsense) throws IOException {
Accounts accounts = adsense.accounts().list().execute();
if (accounts.isEmpty()) {
return null;
}
return accounts.getItems().get(0);
}
private static void updateStats(Context context, boolean bulkInsert, List<AdmobStats> result) {
ContentAdapter contentAdapter = ContentAdapter.getInstance((Application) context
.getApplicationContext());
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);
}
}
}
private static AdSense createBackgroundSyncClient(Context context, String admobAccount,
Bundle extras, String authority, Bundle syncBundle) {
BackgroundGoogleAccountCredential credential = BackgroundGoogleAccountCredential
.usingOAuth2(context, Collections.singleton(AdSenseScopes.ADSENSE_READONLY),
extras, authority, syncBundle);
credential.setSelectedAccountName(admobAccount);
AdSense adsense = new AdSense.Builder(AndroidHttp.newCompatibleTransport(), JSON_FACTORY,
credential).setApplicationName(APPLICATION_NAME).build();
return adsense;
}
private static List<AdmobStats> generateReport(AdSense adsense, Account account,
String adClientId, Date startDate, Date endDate) throws IOException, ParseException {
String startDateStr = DATE_FORMATTER.format(startDate);
String endDateStr = DATE_FORMATTER.format(endDate);
Generate request = adsense.accounts().reports()
.generate(account.getId(), startDateStr, endDateStr);
// Specify the desired ad client using a filter.
request.setFilter(Arrays.asList("AD_CLIENT_ID==" + escapeFilterParameter(adClientId)));
request.setDimension(Arrays.asList("DATE", "AD_UNIT_ID", "AD_UNIT_CODE", "AD_UNIT_NAME"));
request.setMetric(Arrays.asList("PAGE_VIEWS", "AD_REQUESTS", "AD_REQUESTS_COVERAGE",
"CLICKS", "AD_REQUESTS_CTR", "COST_PER_CLICK", "AD_REQUESTS_RPM", "EARNINGS",
"INDIVIDUAL_AD_IMPRESSIONS"));
// Sort by ascending date.
request.setSort(Arrays.asList("+DATE"));
request.setUseTimezoneReporting(true);
AdsenseReportsGenerateResponse response = request.execute();
List<AdmobStats> result = new ArrayList<AdmobStats>();
if (response.getRows() == null || response.getRows().isEmpty()) {
Log.d(TAG, "AdSense API returned no rows.");
}
if (DEBUG) {
StringBuilder buff = new StringBuilder();
for (AdsenseReportsGenerateResponse.Headers header : response.getHeaders()) {
buff.append(String.format("%25s", header.getName()));
if (header.getCurrency() != null) {
buff.append(" " + header.getCurrency());
}
}
Log.d(TAG, "");
}
String currencyCode = null;
AdsenseReportsGenerateResponse.Headers revenueHeader = response.getHeaders().get(11);
if (revenueHeader != null && revenueHeader.getCurrency() != null) {
currencyCode = revenueHeader.getCurrency();
}
for (List<String> row : response.getRows()) {
if (DEBUG) {
StringBuilder buff = new StringBuilder();
for (String column : row) {
buff.append(String.format("%25s", column));
}
Log.d(TAG, buff.toString());
}
AdmobStats admob = new AdmobStats();
admob.setDate(DATE_FORMATTER.parse(row.get(0)));
admob.setSiteId(row.get(1));
admob.setRequests(Utils.tryParseInt(row.get(5)));
admob.setFillRate(Utils.tryParseFloat(row.get(6)));
admob.setClicks(Utils.tryParseInt(row.get(7)));
admob.setCtr(Utils.tryParseFloat(row.get(8)));
admob.setCpcRevenue(Utils.tryParseFloat(row.get(9)));
admob.setEcpm(Utils.tryParseFloat(row.get(10)));
admob.setRevenue(Utils.tryParseFloat(row.get(11)));
admob.setImpressions(Utils.tryParseInt(row.get(12)));
admob.setCurrencyCode(currencyCode);
result.add(admob);
}
return result;
}
public static String escapeFilterParameter(String parameter) {
return parameter.replace("\\", "\\\\").replace(",", "\\,");
}
public static Map<String, String> getAdUnits(Context context, String admobAccount)
throws IOException {
AdSense adsense = createForegroundSyncClient(context, admobAccount);
Account account = getFirstAccount(adsense);
if (account == null) {
return new HashMap<String, String>();
}
String adClientId = getClientId(adsense, account);
if (adClientId == null) {
// XXX throw?
return new HashMap<String, String>();
}
AdUnits units = adsense.accounts().adunits().list(account.getId(), adClientId)
.setMaxResults(MAX_LIST_PAGE_SIZE).setPageToken(null).execute();
List<AdUnit> items = units.getItems();
// preserver order
Map<String, String> result = new LinkedHashMap<String, String>();
for (AdUnit unit : items) {
result.put(unit.getId(), unit.getName());
}
return result;
}
}