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; } }