private static void performSync( Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) throws OperationCanceledException { try { DevConsoleV2 console = DevConsoleRegistry.getInstance().get(account.name); if (console != null) { List<AppInfo> appDownloadInfos = console.getAppInfo(null); // this can also happen if authentication fails and the user // need to click on a notification to confirm or re-enter // password (e.g., if password changed or 2FA enabled) if (appDownloadInfos.isEmpty()) { return; } Log.d(TAG, "andlytics from sync adapter, size: " + appDownloadInfos.size()); List<AppStatsDiff> diffs = new ArrayList<AppStatsDiff>(); Map<String, List<String>> admobAccountSiteMap = new HashMap<String, List<String>>(); db = ContentAdapter.getInstance(AndlyticsApp.getInstance()); for (AppInfo appDownloadInfo : appDownloadInfos) { // update in database diffs.add(db.insertOrUpdateStats(appDownloadInfo)); String[] admobDetails = AndlyticsDb.getInstance(context).getAdmobDetails(appDownloadInfo.getPackageName()); if (admobDetails != null) { String admobAccount = admobDetails[0]; String admobSiteId = admobDetails[1]; if (admobAccount != null) { List<String> siteList = admobAccountSiteMap.get(admobAccount); if (siteList == null) { siteList = new ArrayList<String>(); } siteList.add(admobSiteId); admobAccountSiteMap.put(admobAccount, siteList); } } // update app details AndlyticsDb.getInstance(context).insertOrUpdateAppDetails(appDownloadInfo); } Log.d(TAG, "sucessfully synced andlytics"); // check for notifications NotificationHandler.handleNotificaions(context, diffs, account.name); if (!admobAccountSiteMap.isEmpty()) { Log.d(TAG, "Syncing AdMob stats"); // sync admob accounts Set<String> admobAccuntKeySet = admobAccountSiteMap.keySet(); for (String admobAccount : admobAccuntKeySet) { AdmobRequest.syncSiteStats( admobAccount, context, admobAccountSiteMap.get(admobAccount), null); } Log.d(TAG, "Sucessfully synced AdMob stats"); } DeveloperAccountManager.getInstance(context) .saveLastStatsRemoteUpdateTime(account.name, System.currentTimeMillis()); } } catch (DevConsoleException e) { Log.e(TAG, "error during sync", e); } catch (AdmobException e) { Log.e(TAG, "error during Admob sync", e); } }
/** * Parses the supplied JSON string and builds a list of apps from it * * @param json * @param accountName * @param skipIncomplete * @return List of apps * @throws JSONException */ static List<AppInfo> parseAppInfos(String json, String accountName, boolean skipIncomplete) throws JSONException { Date now = new Date(); List<AppInfo> apps = new ArrayList<AppInfo>(); // Extract the base array containing apps JSONObject result = new JSONObject(json).getJSONObject("result"); if (DEBUG) { pp("result", result); } JSONArray jsonApps = result.optJSONArray("1"); if (DEBUG) { pp("jsonApps", jsonApps); } if (jsonApps == null) { // no apps yet? return apps; } int numberOfApps = jsonApps.length(); Log.d(TAG, String.format("Found %d apps in JSON", numberOfApps)); for (int i = 0; i < numberOfApps; i++) { AppInfo app = new AppInfo(); app.setAccount(accountName); app.setLastUpdate(now); // Per app: // 1 : { 1: package name, // 2 : { 1: [{1 : lang, 2: name, 3: description, 4: ??, 5: what's new}], 2 : ?? }, // 3 : ??, // 4 : update history, // 5 : price, // 6 : update date, // 7 : state? // } // 2 : {} // 3 : { 1: active dnd, 2: # ratings, 3: avg rating, 4: ???, 5: total dnd } // arrays have changed to objects, with the index as the key /* * Per app: * null * [ APP_INFO_ARRAY * * null * * packageName * * Nested array with details * * null * * Nested array with version details * * Nested array with price details * * Last update Date * * Number [1=published, 5 = draft?] * ] * null * [ APP_STATS_ARRAY * * null, * * Active installs * * Total ratings * * Average rating * * Errors * * Total installs * ] */ JSONObject jsonApp = jsonApps.getJSONObject(i); JSONObject jsonAppInfo = jsonApp.getJSONObject("1"); if (DEBUG) { pp("jsonAppInfo", jsonAppInfo); } String packageName = jsonAppInfo.getString("1"); // Look for "tmp.7238057230750432756094760456.235728507238057230542" if (packageName == null || (packageName.startsWith("tmp.") && Character.isDigit(packageName.charAt(4)))) { Log.d(TAG, String.format("Skipping draft app %d, package name=%s", i, packageName)); continue; // Draft app } // Check number code and last updated date // Published: 1 // Unpublished: 2 // Draft: 5 // Draft w/ in-app items?: 6 // TODO figure out the rest and add don't just skip, filter, etc. Cf. #223 int publishState = jsonAppInfo.optInt("7"); Log.d(TAG, String.format("%s: publishState=%d", packageName, publishState)); if (publishState != 1) { // Not a published app, skipping Log.d( TAG, String.format( "Skipping app %d with state != 1: package name=%s: state=%d", i, packageName, publishState)); continue; } app.setPublishState(publishState); app.setPackageName(packageName); /* * Per app details: * 1: Country code * 2: App Name * 3: Description * 4: Promo text * 5: Last what's new */ // skip if we can't get all the data // XXX should we just let this crash so we know there is a problem? if (!jsonAppInfo.has("2")) { if (skipIncomplete) { Log.d( TAG, String.format( "Skipping app %d because no app details found: package name=%s", i, packageName)); } else { Log.d(TAG, "Adding incomplete app: " + packageName); apps.add(app); } continue; } if (!jsonAppInfo.has("4")) { if (skipIncomplete) { Log.d( TAG, String.format( "Skipping app %d because no versions info found: package name=%s", i, packageName)); } else { Log.d(TAG, "Adding incomplete app: " + packageName); apps.add(app); } continue; } JSONObject appDetails = jsonAppInfo.getJSONObject("2").getJSONArray("1").getJSONObject(0); if (DEBUG) { pp("appDetails", appDetails); } app.setName(appDetails.getString("2")); String description = appDetails.getString("3"); String changelog = appDetails.optString("5"); Long lastPlayStoreUpdate = jsonAppInfo.getJSONObject("11").getLong("1"); AppDetails details = new AppDetails(description, changelog, lastPlayStoreUpdate); app.setDetails(details); /* * Per app version details: * null * null * packageName * versionNumber * versionName * null * Array with app icon [null,null,null,icon] */ // XXX JSONArray appVersions = jsonAppInfo.getJSONObject("4").getJSONObject("1").optJSONArray("1"); if (DEBUG) { pp("appVersions", appVersions); } if (appVersions == null) { if (skipIncomplete) { Log.d( TAG, String.format( "Skipping app %d because no versions info found: package name=%s", i, packageName)); } else { Log.d(TAG, "Adding incomplete app: " + packageName); apps.add(app); } continue; } JSONObject lastAppVersionDetails = appVersions.getJSONObject(appVersions.length() - 1).getJSONObject("2"); if (DEBUG) { pp("lastAppVersionDetails", lastAppVersionDetails); } app.setVersionName(lastAppVersionDetails.getString("4")); app.setIconUrl(lastAppVersionDetails.getJSONObject("6").getString("3")); // App stats /* * null, * Active installs * Total ratings * Average rating * Errors * Total installs */ // XXX this index might not be correct for all apps? // 3 : { 1: active dnd, 2: # ratings, 3: avg rating, 4: #errors?, 5: total dnd } JSONObject jsonAppStats = jsonApp.optJSONObject("3"); if (DEBUG) { pp("jsonAppStats", jsonAppStats); } if (jsonAppStats == null) { if (skipIncomplete) { Log.d( TAG, String.format( "Skipping app %d because no stats found: package name=%s", i, packageName)); } else { Log.d(TAG, "Adding incomplete app: " + packageName); apps.add(app); } continue; } AppStats stats = new AppStats(); stats.setDate(now); if (jsonAppStats.length() < 4) { // no statistics (yet?) or weird format // TODO do we need differentiate? stats.setActiveInstalls(0); stats.setTotalDownloads(0); stats.setNumberOfErrors(0); } else { stats.setActiveInstalls(jsonAppStats.getInt("1")); stats.setTotalDownloads(jsonAppStats.getInt("5")); stats.setNumberOfErrors(jsonAppStats.optInt("4")); } app.setLatestStats(stats); apps.add(app); } return apps; }
/** * Parses the supplied JSON string and builds a list of apps from it * * @param json * @param accountName * @return List of apps * @throws JSONException */ static List<AppInfo> parseAppInfos(String json, String accountName) throws JSONException { Date now = new Date(); List<AppInfo> apps = new ArrayList<AppInfo>(); // Extract the base array containing apps JSONArray jsonApps = new JSONObject(json).getJSONArray("result").getJSONArray(1); if (DEBUG) { pp("jsonApps", jsonApps); } int numberOfApps = jsonApps.length(); for (int i = 0; i < numberOfApps; i++) { AppInfo app = new AppInfo(); app.setAccount(accountName); app.setLastUpdate(now); /* * Per app: * null * [ APP_INFO_ARRAY * * null * * packageName * * Nested array with details * * null * * Nested array with version details * * Nested array with price details * * Last update Date * * Number [1=published, 5 = draft?] * ] * null * [ APP_STATS_ARRAY * * null, * * Active installs * * Total ratings * * Average rating * * Errors * * Total installs * ] */ JSONArray jsonApp = jsonApps.getJSONArray(i); JSONArray jsonAppInfo = jsonApp.getJSONArray(1); if (DEBUG) { pp("jsonAppInfo", jsonAppInfo); } String packageName = jsonAppInfo.getString(1); // Look for "tmp.7238057230750432756094760456.235728507238057230542" if (packageName == null || (packageName.startsWith("tmp.") && Character.isDigit(packageName.charAt(4)))) { continue; // Draft app } // Check number code and last updated date // Published: 1 // Unpublished: 2 // Draft: 5 // Draft w/ in-app items?: 6 // TODO figure out the rest and add don't just skip, filter, etc. Cf. #223 int publishState = jsonAppInfo.getInt(7); Log.d(TAG, String.format("%s: publishState=%d", packageName, publishState)); if (publishState != 1) { // Not a published app, skipping continue; } app.setPublishState(publishState); app.setPackageName(packageName); /* * Per app details: * null * Country code * App Name * Description * Unknown * Last what's new */ if (jsonAppInfo.length() < 5) { // skip if we can't get all the data continue; } JSONArray appDetails = jsonAppInfo.getJSONArray(2).getJSONArray(1).getJSONArray(0); if (DEBUG) { pp("appDetails", appDetails); } app.setName(appDetails.getString(2)); /* * Per app version details: * null * null * packageName * versionNumber * versionName * null * Array with app icon [null,null,null,icon] */ JSONArray appVersions = jsonAppInfo.optJSONArray(4); if (DEBUG) { pp("appVersions", appVersions); } if (appVersions == null) { continue; } JSONArray lastAppVersionDetails = appVersions.getJSONArray(appVersions.length() - 1).getJSONArray(2); if (DEBUG) { pp("lastAppVersionDetails", lastAppVersionDetails); } app.setVersionName(lastAppVersionDetails.getString(4)); app.setIconUrl(lastAppVersionDetails.getJSONArray(6).getString(3)); // App stats /* * null, * Active installs * Total ratings * Average rating * Errors * Total installs */ // XXX this index might not be correct for all apps? JSONArray jsonAppStats = jsonApp.optJSONArray(3); if (DEBUG) { pp("jsonAppStats", jsonAppStats); } if (jsonAppStats == null) { continue; } AppStats stats = new AppStats(); stats.setRequestDate(now); if (jsonAppStats.length() < 6) { // no statistics (yet?) or weird format // TODO do we need differentiate? stats.setActiveInstalls(0); stats.setTotalDownloads(0); stats.setNumberOfErrors(0); } else { stats.setActiveInstalls(jsonAppStats.getInt(1)); stats.setTotalDownloads(jsonAppStats.getInt(5)); stats.setNumberOfErrors(jsonAppStats.optInt(4)); } app.setLatestStats(stats); apps.add(app); } return apps; }