Java tutorial
/* * XboxLiveParser.java * Copyright (C) 2010-2014 Akop Karapetyan * * This file is part of Spark 360, the online gaming service client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA. * */ package com.akop.bach.parser; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import com.akop.bach.App; import com.akop.bach.BasicAccount; import com.akop.bach.Preferences; import com.akop.bach.R; import com.akop.bach.XboxLive; import com.akop.bach.XboxLive.Achievements; import com.akop.bach.XboxLive.Beacons; import com.akop.bach.XboxLive.ComparedAchievementInfo; import com.akop.bach.XboxLive.ComparedGameInfo; import com.akop.bach.XboxLive.Friends; import com.akop.bach.XboxLive.FriendsOfFriend; import com.akop.bach.XboxLive.GameOverviewInfo; import com.akop.bach.XboxLive.GamerProfileInfo; import com.akop.bach.XboxLive.Games; import com.akop.bach.XboxLive.LiveStatusInfo; import com.akop.bach.XboxLive.Messages; import com.akop.bach.XboxLive.NotifyStates; import com.akop.bach.XboxLive.Profiles; import com.akop.bach.XboxLive.RecentPlayers; import com.akop.bach.XboxLive.SentMessages; import com.akop.bach.XboxLiveAccount; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.cookie.BasicClientCookie; import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; public class XboxLiveParser extends LiveParser { private interface Parseable { void doParse() throws AuthenticationException, IOException, ParserException; } private interface ParseableWithResult { Object doParse() throws AuthenticationException, IOException, ParserException; } private static final String URL_SECRET_ACHIEVE_TILE = "http://live.xbox.com/Content/Images/HiddenAchievement.png"; private static final String URL_JSON_PROFILE = "https://live.xbox.com/Handlers/ShellData.ashx?culture=%1$s" + "&XBXMChg=%2$d&XBXNChg=%2$d&XBXSPChg=%2$d&XBXChg=%2$d" + "&leetcallback=jsonp1287728723001"; private static final String URL_JSON_READ_MESSAGE = "https://live.xbox.com/%1$s/Messages/Message"; private static final String URL_JSON_DELETE_MESSAGE = "https://live.xbox.com/%1$s/Messages/Delete"; private static final String URL_JSON_BLOCK_MESSAGE = "https://live.xbox.com/%1$s/Messages/Block"; private static final String URL_JSON_SEND_MESSAGE = "https://live.xbox.com/%1$s/Messages/SendMessage"; private static final String URL_JSON_FRIEND_REQUEST = "https://live.xbox.com/%1$s/Friends/%2$s"; private static final String URL_JSON_RECENT_LIST = "https://live.xbox.com/%1$s/Friends/Recent"; private static final String URL_JSON_FOF_LIST = "https://live.xbox.com/%1$s/Friends/List"; private static final String URL_JSON_MESSAGE_LIST = "https://live.xbox.com/%1$s/Messages/GetMessages"; private static final String URL_JSON_FRIEND_LIST = "https://live.xbox.com/%1$s/Friends/List"; private static final String URL_JSON_COMPARE_GAMES = "https://live.xbox.com/%1$s/Activity/Summary?CompareTo=%2$s"; private static final String URL_GAME_LIST = "https://account.xbox.com/%1$s/Achievements?xr=socialtwistnav"; private static final String URL_JSON_BEACONS = "https://live.xbox.com/%1$s/Beacons/JumpInList"; private static final String URL_JSON_BEACON_SET = "https://live.xbox.com/%1$s/Beacons/Set"; private static final String URL_JSON_BEACON_CLEAR = "https://live.xbox.com/%1$s/Beacons/Clear"; private static final String URL_VTOKEN_MESSAGES = "https://live.xbox.com/%1$s/Messages?xr=socialtwistnav"; private static final String URL_VTOKEN_ACTIVITY = "https://live.xbox.com/%1$s/Activity?xr=socialtwistnav"; private static final String URL_VTOKEN_FRIENDS = "https://live.xbox.com/%1$s/Home?xr=socialtwistnav"; private static final String URL_VTOKEN_COMPARE_GAMES = "https://live.xbox.com/%1$s/Activity?compareTo=%2$s"; private static final String URL_VTOKEN_FRIEND_REQUEST = "https://live.xbox.com/%1$s/Profile?gamertag=%2$s"; private static final String URL_JSON_PROFILE_REFERER = "https://live.xbox.com/en-US/MyXbox"; private static final String URL_MY_PROFILE = "https://account.xbox.com/%1$s/Profile/"; private static final String URL_STATUS = "https://support.xbox.com/%1$s/xbox-live-status"; private static final String URL_ACHIEVEMENTS = "https://live.xbox.com/%1$s/Activity/Details?titleId=%2$s"; private static final String URL_COMPARE_ACHIEVEMENTS = "https://live.xbox.com/%1$s/Activity/Details?compareTo=%3$s&titleId=%2$s"; private static final String URL_GAMERCARD = "http://gamercard.xbox.com/%1$s/%2$s.card"; private static final String URL_GAMERPIC = "http://avatar.xboxlive.com/avatar/%s/avatarpic-l.png"; private static final String URL_AVATAR_BODY = "http://avatar.xboxlive.com/avatar/%s/avatar-body.png"; private static final String URL_EDIT_PROFILE = "https://live.xbox.com/%1$s/MyXbox/GamerProfile"; private static final String FRIEND_MANAGER_ADD = "Add"; private static final String FRIEND_MANAGER_REMOVE = "Remove"; private static final String FRIEND_MANAGER_ACCEPT = "Accept"; private static final String FRIEND_MANAGER_REJECT = "Decline"; private static final String FRIEND_MANAGER_CANCEL = "Cancel"; private static final Pattern PATTERN_GAMERPIC_CLASSIC = Pattern.compile("/(1)(\\d+)$"); private static final Pattern PATTERN_GAMERPIC_AVATAR = Pattern.compile("/avatarpic-(s)(.png)$", Pattern.CASE_INSENSITIVE); private static final Pattern PATTERN_COMPARE_ACH_JSON = Pattern .compile("broker\\.publish\\(routes\\.activity\\.details\\.load\\, (.*)\\);\\s*\\}\\);"); private static final Pattern PATTERN_ACH_JSON = Pattern .compile("broker\\.publish\\(routes\\.activity\\.details\\.load\\, (.*)\\);\\s*\\}\\);"); private static final Pattern PATTERN_LOADBAL_ICON = Pattern.compile("^http://([0-9\\.]+/)"); private static final Pattern PATTERN_GAMERCARD_REP = Pattern.compile("class=\"Star ([^\"]*)\""); private static final Pattern PATTERN_GAME_OVERVIEW_TITLE = Pattern.compile("<h1>([^<]*)</h1>"); private static final Pattern PATTERN_GAME_OVERVIEW_DESCRIPTION = Pattern .compile("<div class=\"Text\">\\s*<p\\s*[^>]*>([^<]+)</p>\\s*</div>"); private static final Pattern PATTERN_GAME_OVERVIEW_MANUAL = Pattern .compile("<a class=\"Manual\" href=\"([^\"]+)\""); private static final Pattern PATTERN_GAME_OVERVIEW_ESRB = Pattern .compile("<img alt=\"([^\"]*)\" class=\"ratingLogo\" src=\"([^\"]*)\""); private static final Pattern PATTERN_GAME_OVERVIEW_IMAGE = Pattern .compile("<div id=\"image\\d+\" class=\"TabPage\">\\s*<img (?:width=\"[^\"]*\" )?src=\"([^\"]*)\""); private static final Pattern PATTERN_GAME_OVERVIEW_BANNER = Pattern .compile("<img src=\"([^\"]*)\" alt=\"[^\"]*\" class=\"Banner\" />"); private static final Pattern PATTERN_STATUS_ITEM = Pattern.compile( "<div class=\"item\">\\s+<h3>(?:<a href=\"[^\"]+\">)?([^<]+)(?:</a>)?<span class=\"([^\"]+)\">([^<]+)</span></h3>(?:\\s+<div class=\"details\">(.*?)</div>)?", Pattern.DOTALL); private static final Pattern PATTERN_GAME_OVERVIEW_REDIRECTING_URL = Pattern.compile("/Title/\\d+$"); private static final Pattern PATTERN_GAME_ITEM = Pattern .compile("<div class=\"titleTile [^\"]*\">(.*?)</div>\\s*</div>\\s*</div>", Pattern.DOTALL); private static final Pattern PATTERN_GAME_TITLE_ID = Pattern .compile("<div class=\"titleName\"><a href=\"/en-US/Achievements/Xbox/(\\d+)\">([^<]+)</a></div>"); private static final Pattern PATTERN_GAME_BOX_ART = Pattern.compile("<img src=\"([^\"]+)\" />"); private static final Pattern PATTERN_GAME_SCORE = Pattern .compile("<div class=\"score\"><img src=\"[^\"]+\" /><span>(\\d+)</span></div>"); private static final Pattern PATTERN_GAME_ACHIEVEMENTS = Pattern .compile("<div class=\"achievements\"><img src=\"[^\"]+\" /><span>(\\d+)</span>"); private static final int COLUMN_GAME_ID = 0; private static final int COLUMN_GAME_LAST_PLAYED_DATE = 1; private static final int COLUMN_GAME_UID = 2; private static final int COLUMN_GAME_ACHIEVEMENTS_ACQUIRED = 3; private static final int COLUMN_GAME_ACHIEVEMENTS_TOTAL = 4; private static final String[] GAMES_PROJECTION = new String[] { Games._ID, Games.LAST_PLAYED, Games.UID, Games.ACHIEVEMENTS_UNLOCKED, Games.ACHIEVEMENTS_TOTAL }; private static final int COLUMN_FRIEND_ID = 0; public static final int MAX_BEACONS = 3; private static final String BOXART_TEMPLATE = "http://tiles.xbox.com/consoleAssets/%1$X/%2$s/%3$s"; private static final String[] FRIENDS_PROJECTION = new String[] { Friends._ID }; private String mLocale; public XboxLiveParser(Context context) { super(context); String language = Locale.getDefault().getLanguage(); String country = Locale.getDefault().getCountry(); mLocale = context.getString(R.string.xbox_live_locale); if (App.getConfig().logToConsole()) App.logv("XboxLiveParser: using " + mLocale + " locale (default: " + language + "-" + country + ")"); } @Override protected void initRequest(HttpUriRequest request) { super.initRequest(request); // Set the timezone cookie TimeZone tz = TimeZone.getDefault(); int utcOffsetMinutes = tz.getOffset(System.currentTimeMillis()) / (1000 * 60); BasicClientCookie cookie = new BasicClientCookie("UtcOffsetMinutes", String.valueOf(utcOffsetMinutes)); cookie.setPath("/"); cookie.setDomain(".xbox.com"); mHttpClient.getCookieStore().addCookie(cookie); } @Override protected String getReplyToPage() { return "https://live.xbox.com/xweb/live/passport/setCookies.ashx"; } private boolean getXboxJsonStatus(String url, List<NameValuePair> inputs) throws IOException, ParserException { String page = getResponse(url, inputs, true); JSONObject json = getJSONObject(page, false); if (!json.optBoolean("Success")) return false; return true; } private JSONObject getXboxJsonObject(String page) throws ParserException { JSONObject json = getJSONObject(page, false); if (!json.optBoolean("Success")) return null; return json.optJSONObject("Data"); } private JSONArray getXboxJsonArray(String url, List<NameValuePair> inputs) throws ParserException, IOException { String page = getResponse(url, inputs, true); JSONObject json = getJSONObject(page, false); if (!json.optBoolean("Success")) return null; return json.optJSONArray("Data"); } public static String getLargeGamerpic(String iconUrl) { Matcher m; // Non-avatar (classic) gamerpic if ((m = PATTERN_GAMERPIC_CLASSIC.matcher(iconUrl)).find()) return String.format("%s2%s", iconUrl.substring(0, m.start(1)), m.group(2)); // Avatar (NXE) gamerpic else if ((m = PATTERN_GAMERPIC_AVATAR.matcher(iconUrl)).find()) return String.format("%sl%s", iconUrl.substring(0, m.start(1)), m.group(2)); if (App.getConfig().logToConsole()) App.logv("%s has an unrecognized format; returning original", iconUrl); return iconUrl; } public static String getStandardIcon(String loadBalIcon) { if (loadBalIcon == null) return null; Matcher m; if (!(m = PATTERN_LOADBAL_ICON.matcher(loadBalIcon)).find()) return loadBalIcon; String replacement = loadBalIcon.substring(0, m.start(1)) + loadBalIcon.substring(m.end(1)); return replacement; } public static String getAvatarUrl(String gamertag) { if (gamertag == null) return null; try { return String.format(URL_AVATAR_BODY, URLEncoder.encode(gamertag, "UTF-8")).replace("+", "%20"); } catch (UnsupportedEncodingException e) { return null; } } public static String getGamerpicUrl(String gamertag) { if (gamertag == null) return null; try { return String.format(URL_GAMERPIC, URLEncoder.encode(gamertag, "UTF-8")).replace("+", "%20"); } catch (UnsupportedEncodingException e) { return null; } } public String getGamercardUrl(String gamertag) { if (gamertag == null) return null; try { return String.format(URL_GAMERCARD, mLocale, URLEncoder.encode(gamertag, "UTF-8")).replace("+", "%20"); } catch (UnsupportedEncodingException e) { return null; } } private long parseTicks(String str) { if (str == null) return 0; int startPos, endPos; if ((startPos = str.indexOf("(")) < 0) return 0; if ((endPos = str.lastIndexOf(")")) < 0) return 0; long ticks; try { ticks = Long.parseLong(str.substring(startPos + 1, endPos)); } catch (Exception e) { ticks = 0; } return ticks; } private String getVTokenFromContents(String page) throws IOException, ParserException { List<NameValuePair> inputs = new ArrayList<NameValuePair>(10); getInputs(page, inputs, null); for (NameValuePair pair : inputs) if (pair.getName().equals("__RequestVerificationToken")) return pair.getValue(); if (App.getConfig().logToConsole()) App.logv("Token parsing failed"); throw new TokenException(mContext); } private String getVToken(String url) throws IOException, ParserException { boolean retried = false; String token = null; do { String page; try { page = getResponse(url); } catch (ParserException e) { if (App.getConfig().logToConsole()) { App.logv("Error fetching token from URL " + url); e.printStackTrace(); } throw new TokenException(mContext); } try { token = getVTokenFromContents(page); } catch (TokenException ex) { if (retried) throw ex; if (App.getConfig().logToConsole()) App.logv("Token parsing initially failed; retrying"); retried = true; continue; } } while (false); return token; } private String getBoxArt(String titleId, boolean largeBoxart) { if (titleId == null) return null; long titleAsNumber; try { titleAsNumber = Long.parseLong(titleId); } catch (Exception e) { if (App.getConfig().logToConsole()) App.logv("getBoxArt: " + titleId + " cannot be parsed as integer"); return null; } return getBoxArt(titleAsNumber, largeBoxart); } private String getBoxArt(long titleId, boolean largeBoxart) { if (titleId < 1) return null; String jpgFile = (largeBoxart) ? "largeboxart.jpg" : "smallboxart.jpg"; return String.format(BOXART_TEMPLATE, titleId, mLocale, jpgFile); } private ContentValues[] parseGetBeacons(String gamertag, String token) throws IOException, ParserException { long started = System.currentTimeMillis(); ContentResolver cr = mContext.getContentResolver(); String url = String.format(URL_JSON_BEACONS, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "__RequestVerificationToken", token); if (gamertag != null) addValue(inputs, "gamertag", gamertag); JSONArray activities = getXboxJsonArray(url, inputs); if (App.getConfig().logToConsole()) started = displayTimeTaken("Beacon page fetch", started); List<ContentValues> cvList = new ArrayList<ContentValues>(); if (activities != null) { for (int i = 0, n = activities.length(); i < n; i++) { JSONObject activity = activities.optJSONObject(i); String titleId; JSONObject beacon; if (activity == null || (titleId = activity.optString("titleId")) == null || (beacon = activity.optJSONObject("beacon")) == null) { continue; } ContentValues cv = new ContentValues(); cv.put(Beacons.TITLE_ID, titleId); cv.put(Beacons.TITLE_NAME, activity.optString("titleName")); cv.put(Beacons.TITLE_BOXART, getBoxArt(titleId, false)); cv.put(Beacons.TEXT, beacon.isNull("text") ? null : beacon.optString("text")); cvList.add(cv); } cr.notifyChange(Games.CONTENT_URI, null); } if (App.getConfig().logToConsole()) started = displayTimeTaken("Beacon data collection", started); ContentValues[] cvArray = new ContentValues[cvList.size()]; cvList.toArray(cvArray); return cvArray; } private void parseSendMessage(XboxLiveAccount account, String[] recipients, String body) throws IOException, ParserException { if (!account.isGold()) throw new ParserException(mContext, R.string.account_cant_send); long started = System.currentTimeMillis(); String token = getVToken(String.format(URL_VTOKEN_MESSAGES, mLocale)); String url = String.format(URL_JSON_SEND_MESSAGE, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(100); // Add recipients for (String recipient : recipients) addValue(inputs, "recipients", recipient); // Add message addValue(inputs, "message", body); // Req. ver. token addValue(inputs, "__RequestVerificationToken", token); String page = getResponse(url, inputs, true); if (App.getConfig().logToConsole()) started = displayTimeTaken("Send message page fetch", started); JSONObject json = getJSONObject(page, false); try { if (!json.getBoolean("Success")) { if (App.getConfig().logToConsole()) App.logv("XboxLiveParser/parseSendMessage: Parser error: " + page); throw new ParserException( json.optString("Status", mContext.getString(R.string.message_could_not_be_sent))); } String preview = ""; if (body != null) { preview = body.replaceAll("\\W", " "); if (preview.length() > 20) preview = preview.substring(0, 19); } ContentValues cv = new ContentValues(10); cv.put(SentMessages.ACCOUNT_ID, account.getId()); cv.put(SentMessages.RECIPIENTS, joinString(recipients, ",")); cv.put(SentMessages.SENT, System.currentTimeMillis()); cv.put(SentMessages.PREVIEW, preview); cv.put(SentMessages.BODY, body); try { ContentResolver cr = mContext.getContentResolver(); cr.insert(SentMessages.CONTENT_URI, cv); } catch (Exception e) { if (App.getConfig().logToConsole()) e.printStackTrace(); } } catch (JSONException e) { if (App.getConfig().logToConsole()) { App.logv("XboxLiveParser/parseSendMessage: JSON error: " + page); e.printStackTrace(); } } if (App.getConfig().logToConsole()) displayTimeTaken("Message send processing", started); } private ContentValues parseSummaryData(XboxLiveAccount account) throws ParserException, IOException { long started = System.currentTimeMillis(); String url = String.format(URL_JSON_PROFILE, mLocale, System.currentTimeMillis()); HttpUriRequest request = new HttpGet(url); request.addHeader("Referer", URL_JSON_PROFILE_REFERER); request.addHeader("X-Requested-With", "XMLHttpRequest"); String page = getResponse(request, null); if (App.getConfig().logToConsole()) started = displayTimeTaken("Profile page fetch", started); ContentValues cv = new ContentValues(15); String gamertag; JSONObject json = getJSONObject(page); try { gamertag = json.getString("gamertag"); } catch (JSONException e) { throw new ParserException(mContext, R.string.error_json_parser_error); } cv.put(Profiles.GAMERTAG, gamertag); cv.put(Profiles.ICON_URL, json.optString("gamerpic")); cv.put(Profiles.POINTS_BALANCE, 0); cv.put(Profiles.IS_GOLD, json.optInt("tier") >= 6); cv.put(Profiles.TIER, json.optString("tiertext")); cv.put(Profiles.GAMERSCORE, json.optInt("gamerscore")); cv.put(Profiles.UNREAD_MESSAGES, json.optInt("messages")); cv.put(Profiles.UNREAD_NOTIFICATIONS, json.optInt("notifications")); cv.put(Profiles.REP, 0); return cv; } private void parseAccountSummary(XboxLiveAccount account) throws ParserException, IOException { ContentValues cv = parseSummaryData(account); ContentResolver cr = mContext.getContentResolver(); long accountId = account.getId(); boolean newRecord = true; long started = System.currentTimeMillis(); Cursor c = cr.query(Profiles.CONTENT_URI, new String[] { Profiles._ID }, Profiles.ACCOUNT_ID + "=" + accountId, null, null); if (c != null) { if (c.moveToFirst()) newRecord = false; c.close(); } if (newRecord) { cv.put(Profiles.ACCOUNT_ID, account.getId()); cv.put(Profiles.UUID, account.getUuid()); cr.insert(Profiles.CONTENT_URI, cv); } else { cr.update(Profiles.CONTENT_URI, cv, Profiles.ACCOUNT_ID + "=" + accountId, null); } cr.notifyChange(Profiles.CONTENT_URI, null); if (App.getConfig().logToConsole()) displayTimeTaken("Summary update", started); account.refresh(Preferences.get(mContext)); account.setGamertag(cv.getAsString(Profiles.GAMERTAG)); account.setIconUrl(cv.getAsString(Profiles.ICON_URL)); account.setGoldStatus(cv.getAsBoolean(Profiles.IS_GOLD)); account.setLastSummaryUpdate(System.currentTimeMillis()); account.save(Preferences.get(mContext)); } private void parseToggleBeacon(XboxLiveAccount account, long gameId, boolean setBeacon, String beaconText) throws ParserException, IOException { long started = System.currentTimeMillis(); // Refuse setting more than MAX beacons if (setBeacon && Games.getSetBeaconCount(mContext, account) >= MAX_BEACONS) throw new ParserException(mContext, R.string.too_many_beacons_f, MAX_BEACONS); String titleId = Games.getUid(mContext, gameId); if (titleId == null) throw new ParserException(mContext, R.string.game_not_found_in_collection); String token = getVToken(String.format(URL_VTOKEN_ACTIVITY, mLocale)); String locale = mLocale; String url; if (setBeacon) url = String.format(URL_JSON_BEACON_SET, locale); else url = String.format(URL_JSON_BEACON_CLEAR, locale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "__RequestVerificationToken", token); addValue(inputs, "titleId", titleId); if (setBeacon) addValue(inputs, "text", beaconText); try { if (!getXboxJsonStatus(url, inputs)) throw new ParserException(mContext, R.string.could_not_modify_beacon); } catch (ParserException ex) { throw ex; } catch (Exception ex) { if (App.getConfig().logToConsole()) ex.printStackTrace(); throw new ParserException(mContext, R.string.could_not_modify_beacon); } parseRefreshBeaconData(account, token); if (App.getConfig().logToConsole()) started = displayTimeTaken("Beacon toggle", started); } private void parseRefreshBeaconData(XboxLiveAccount account, String token) throws ParserException, IOException { long started = System.currentTimeMillis(); ContentResolver cr = mContext.getContentResolver(); String url = String.format(URL_JSON_BEACONS, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "__RequestVerificationToken", token); JSONArray activities = getXboxJsonArray(url, inputs); if (App.getConfig().logToConsole()) started = displayTimeTaken("Beacon page fetch", started); if (activities != null) { // Unset all current beacons ContentValues cv = new ContentValues(); cv.put(Games.BEACON_SET, 0); cv.put(Games.BEACON_TEXT, (String) null); cr.update(Games.CONTENT_URI, cv, Games.ACCOUNT_ID + "=" + account.getId(), null); for (int i = 0, n = activities.length(); i < n; i++) { JSONObject activity = activities.optJSONObject(i); String titleId; JSONObject beacon; if (activity == null || (titleId = activity.optString("titleId")) == null || (beacon = activity.optJSONObject("beacon")) == null) { continue; } Long gameId = Games.getId(mContext, account, titleId); if (gameId == null) continue; cv = new ContentValues(); cv.put(Games.BEACON_SET, 1); cv.put(Games.BEACON_TEXT, beacon.optString("text")); cr.update(Games.CONTENT_URI, cv, Games._ID + "=" + gameId, null); } cr.notifyChange(Games.CONTENT_URI, null); } if (App.getConfig().logToConsole()) started = displayTimeTaken("Beacon updates", started); } private void parseGames(XboxLiveAccount account) throws ParserException, IOException { long started = System.currentTimeMillis(); String url = String.format(URL_GAME_LIST, mLocale); String page = getResponse(url); if (App.getConfig().logToConsole()) started = displayTimeTaken("Game page fetch", started); long accountId = account.getId(); String[] queryParams = new String[1]; int rowNo = 0; boolean changed = false; Cursor c; ContentValues cv; long updated = System.currentTimeMillis(); List<ContentValues> newCvs = new ArrayList<ContentValues>(100); ContentResolver cr = mContext.getContentResolver(); List<String> gameIds = new ArrayList<String>(50); Matcher m = PATTERN_GAME_ITEM.matcher(page); while (m.find()) { String content = m.group(1); App.logv(" *** found it: " + content); Matcher im = PATTERN_GAME_TITLE_ID.matcher(content); if (!im.find()) continue; String uid = im.group(1); String title = htmlDecode(im.group(2)); String boxArtUrl = null; int gpAcquired = 0; int achUnlocked = 0; gameIds.add(uid); im = PATTERN_GAME_BOX_ART.matcher(content); if (im.find()) boxArtUrl = im.group(1); im = PATTERN_GAME_SCORE.matcher(content); if (im.find()) { try { gpAcquired = Integer.parseInt(im.group(1)); } catch (NumberFormatException e) { } } im = PATTERN_GAME_ACHIEVEMENTS.matcher(content); if (im.find()) { try { achUnlocked = Integer.parseInt(im.group(1)); } catch (NumberFormatException e) { } } // Check to see if we already have a record of this game queryParams[0] = uid; c = cr.query(Games.CONTENT_URI, GAMES_PROJECTION, Games.ACCOUNT_ID + "=" + accountId + " AND " + Games.UID + "=?", queryParams, null); try { if (c == null || !c.moveToFirst()) // New game { cv = new ContentValues(15); cv.put(Games.ACCOUNT_ID, accountId); cv.put(Games.TITLE, title); cv.put(Games.UID, uid); cv.put(Games.BOXART_URL, boxArtUrl); cv.put(Games.LAST_PLAYED, 0); cv.put(Games.LAST_UPDATED, updated); cv.put(Games.ACHIEVEMENTS_UNLOCKED, achUnlocked); cv.put(Games.ACHIEVEMENTS_TOTAL, achUnlocked); cv.put(Games.POINTS_ACQUIRED, gpAcquired); cv.put(Games.POINTS_TOTAL, gpAcquired); cv.put(Games.GAME_URL, (String) null); cv.put(Games.INDEX, rowNo); // Games with no achievements do not need achievement refresh cv.put(Games.ACHIEVEMENTS_STATUS, 1); newCvs.add(cv); } else // Existing game { long gameId = c.getLong(COLUMN_GAME_ID); long lastPlayedTicksRec = c.getLong(COLUMN_GAME_LAST_PLAYED_DATE); cv = new ContentValues(15); boolean refreshAchievements = true; if (refreshAchievements) { cv.put(Games.ACHIEVEMENTS_UNLOCKED, achUnlocked); cv.put(Games.ACHIEVEMENTS_TOTAL, achUnlocked); cv.put(Games.POINTS_ACQUIRED, gpAcquired); cv.put(Games.POINTS_TOTAL, gpAcquired); cv.put(Games.ACHIEVEMENTS_STATUS, 1); } cv.put(Games.BEACON_SET, 0); cv.put(Games.BEACON_TEXT, (String) null); cv.put(Games.LAST_PLAYED, 0); cv.put(Games.INDEX, rowNo); cv.put(Games.LAST_UPDATED, updated); cr.update(Games.CONTENT_URI, cv, Games._ID + "=" + gameId, null); changed = true; } } finally { if (c != null) c.close(); } rowNo++; } // Remove games that are no longer present c = cr.query(Games.CONTENT_URI, GAMES_PROJECTION, Games.ACCOUNT_ID + "=" + accountId, null, null); if (c != null) { while (c.moveToNext()) { if (!gameIds.contains(c.getString(COLUMN_GAME_UID))) { // Game is no longer in list of played games; remove it cr.delete(ContentUris.withAppendedId(Games.CONTENT_URI, c.getLong(COLUMN_GAME_ID)), null, null); changed = true; } } c.close(); } if (App.getConfig().logToConsole()) started = displayTimeTaken("Game page processing", started); if (newCvs.size() > 0) { changed = true; ContentValues[] cvs = new ContentValues[newCvs.size()]; newCvs.toArray(cvs); cr.bulkInsert(Games.CONTENT_URI, cvs); if (App.getConfig().logToConsole()) displayTimeTaken("Game page insertion", started); } account.refresh(Preferences.get(mContext)); account.setLastGameUpdate(System.currentTimeMillis()); account.save(Preferences.get(mContext)); if (changed) cr.notifyChange(Games.CONTENT_URI, null); } private void parseAchievements(XboxLiveAccount account, long gameId) throws ParserException, IOException { // Find game record in local DB ContentResolver cr = mContext.getContentResolver(); String gameUid = Games.getUid(mContext, gameId); long updated = System.currentTimeMillis(); long started = System.currentTimeMillis(); String pageUrl = String.format(URL_ACHIEVEMENTS, mLocale, gameUid); String page = getResponse(pageUrl); if (App.getConfig().logToConsole()) started = displayTimeTaken("Achievement page fetch", started); Matcher m; if (!(m = PATTERN_ACH_JSON.matcher(page)).find()) throw new ParserException(mContext, R.string.error_achieves_retrieval); JSONObject data = getJSONObject(m.group(1), false); JSONArray players = data.optJSONArray("Players"); if (players.length() < 1) throw new ParserException(mContext, R.string.error_achieves_retrieval); JSONObject player = players.optJSONObject(0); String gamertag = player.optString("Gamertag"); List<ContentValues> cvList = new ArrayList<ContentValues>(100); JSONArray achieves = data.optJSONArray("Achievements"); for (int i = 0, n = achieves.length(); i < n; i++) { JSONObject achieve = achieves.optJSONObject(i); if (achieve == null || achieve.optString("Id") == null) continue; JSONObject progRoot = achieve.optJSONObject("EarnDates"); if (progRoot == null) continue; JSONObject prog = progRoot.optJSONObject(gamertag); String title; String description; String tileUrl; if (achieve.optBoolean("IsHidden")) { title = mContext.getString(R.string.secret_achieve_title); description = mContext.getString(R.string.secret_achieve_desc); tileUrl = URL_SECRET_ACHIEVE_TILE; } else { title = achieve.optString("Name"); description = achieve.optString("Description"); tileUrl = achieve.optString("TileUrl"); } ContentValues cv = new ContentValues(10); // TODO cv.put(Achievements.UID, achieve.optString("Id")); cv.put(Achievements.GAME_ID, gameId); cv.put(Achievements.TITLE, title); cv.put(Achievements.DESCRIPTION, description); cv.put(Achievements.ICON_URL, tileUrl); cv.put(Achievements.POINTS, achieve.optInt("Score", 0)); if (prog != null) { // Unlocked long earnedOn = 0; if (!prog.optBoolean("IsOffline")) earnedOn = parseTicks(prog.optString("EarnedOn")); cv.put(Achievements.ACQUIRED, earnedOn); cv.put(Achievements.LOCKED, 0); } else { // Locked cv.put(Achievements.ACQUIRED, 0); cv.put(Achievements.LOCKED, 1); } cvList.add(cv); } if (App.getConfig().logToConsole()) started = displayTimeTaken("New achievement parsing", started); ContentValues[] cva = new ContentValues[cvList.size()]; cvList.toArray(cva); cr.delete(Achievements.CONTENT_URI, Achievements.GAME_ID + "=" + gameId, null); // Bulk-insert new achievements cr.bulkInsert(Achievements.CONTENT_URI, cva); if (App.getConfig().logToConsole()) started = displayTimeTaken("New achievement processing", started); // Update game stats JSONObject game = data.optJSONObject("Game"); if (game != null) { ContentValues cv = new ContentValues(10); cv.put(Games.LAST_UPDATED, updated); cv.put(Games.ACHIEVEMENTS_STATUS, 0); cv.put(Games.ACHIEVEMENTS_TOTAL, game.optInt("PossibleAchievements", 0)); cv.put(Games.POINTS_TOTAL, game.optInt("PossibleScore", 0)); JSONObject progRoot = game.optJSONObject("Progress"); if (progRoot != null) { JSONObject progress = game.optJSONObject(gamertag); if (progress != null) { cv.put(Games.ACHIEVEMENTS_UNLOCKED, progress.optInt("Achievements", 0)); cv.put(Games.POINTS_ACQUIRED, progress.optInt("Score", 0)); cv.put(Games.LAST_PLAYED, parseTicks(progress.optString("LastPlayed"))); } } // Write changes cr.update(Games.CONTENT_URI, cv, Games._ID + "=" + gameId, null); } cr.notifyChange(Achievements.CONTENT_URI, null); if (App.getConfig().logToConsole()) displayTimeTaken("Updating Game", started); } private void parseFriendSection(long accountId, JSONArray friends, long updated, List<ContentValues> newCvs, int statusHack) { ContentResolver cr = mContext.getContentResolver(); String[] queryParams = new String[1]; for (int i = 0, n = friends.length(); i < n; i++) { JSONObject friend = friends.optJSONObject(i); if (friend == null) continue; String gamertag = friend.optString("GamerTag"); String gamerpic = friend.optString("LargeGamerTileUrl"); String activity = friend.optString("Presence"); String titleName = null; int gamerscore = friend.optInt("GamerScore", 0); long titleId = 0; JSONObject titleInfo = friend.optJSONObject("TitleInfo"); if (titleInfo != null) { titleId = titleInfo.optLong("Id", 0); titleName = null; if (!titleInfo.isNull("Name")) titleName = titleInfo.optString("Name"); } int statusCode = XboxLive.STATUS_OTHER; if (statusHack < 0) // TODO: HACK! { if (friend.optBoolean("IsOnline")) statusCode = XboxLive.STATUS_ONLINE; else statusCode = XboxLive.STATUS_OFFLINE; } else { statusCode = statusHack; } String statusDescription = Friends.getStatusDescription(mContext, statusCode); ContentValues cv = new ContentValues(15); cv.put(Friends.DELETE_MARKER, updated); cv.put(Friends.GAMERSCORE, gamerscore); cv.put(Friends.CURRENT_ACTIVITY, activity); cv.put(Friends.ICON_URL, gamerpic); cv.put(Friends.STATUS_CODE, statusCode); cv.put(Friends.STATUS, statusDescription); cv.put(Friends.TITLE_ID, titleId); cv.put(Friends.TITLE_NAME, titleName); cv.put(Friends.TITLE_URL, getBoxArt(titleId, false)); // check to see if friend is available locally queryParams[0] = gamertag; Cursor c = cr.query(Friends.CONTENT_URI, FRIENDS_PROJECTION, Friends.ACCOUNT_ID + "=" + accountId + " AND " + Friends.GAMERTAG + "=?", queryParams, null); try { if (c != null && c.moveToFirst()) { // Friend in the system; update record long friendId = c.getLong(COLUMN_FRIEND_ID); cr.update(Friends.CONTENT_URI, cv, Friends._ID + "=" + friendId, null); } else { // New friend cv.put(Friends.GAMERTAG, gamertag); cv.put(Friends.ACCOUNT_ID, accountId); cv.put(Friends.IS_FAVORITE, 0); newCvs.add(cv); } } finally { if (c != null) c.close(); } } } private void parseFriends(XboxLiveAccount account) throws IOException, ParserException { long started = System.currentTimeMillis(); long updated = System.currentTimeMillis(); long accountId = account.getId(); synchronized (XboxLiveParser.class) { String token = getVToken(String.format(URL_VTOKEN_FRIENDS, mLocale)); String url = String.format(URL_JSON_FRIEND_LIST, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "__RequestVerificationToken", token); String page = getResponse(url, inputs, true); JSONObject data = getXboxJsonObject(page); if (data == null) throw new ParserException(mContext, R.string.error_friend_retrieval); ContentResolver cr = mContext.getContentResolver(); List<ContentValues> newCvs = new ArrayList<ContentValues>(100); parseFriendSection(accountId, data.optJSONArray("Friends"), updated, newCvs, -1); parseFriendSection(accountId, data.optJSONArray("Incoming"), updated, newCvs, XboxLive.STATUS_INVITE_RCVD); parseFriendSection(accountId, data.optJSONArray("Outgoing"), updated, newCvs, XboxLive.STATUS_INVITE_SENT); // Remove friends that are missing from list cr.delete(Friends.CONTENT_URI, Friends.DELETE_MARKER + "!=" + updated + " AND " + Friends.ACCOUNT_ID + "=" + accountId, null); if (newCvs.size() > 0) { ContentValues[] cvs = new ContentValues[newCvs.size()]; newCvs.toArray(cvs); cr.bulkInsert(Friends.CONTENT_URI, cvs); if (App.getConfig().logToConsole()) displayTimeTaken("Friend page insertion", started); } account.refresh(Preferences.get(mContext)); account.setLastFriendUpdate(System.currentTimeMillis()); account.save(Preferences.get(mContext)); // A friend list is very likely to change at every sync, so // we just do it no matter what cr.notifyChange(Friends.CONTENT_URI, null); } if (App.getConfig().logToConsole()) started = displayTimeTaken("Friend page processing", started); } private void parseUpdateProfile(XboxLiveAccount account, String motto, String name, String location, String bio) throws ParserException, IOException { long started = System.currentTimeMillis(); String url = String.format(URL_EDIT_PROFILE, mLocale); String page = getResponse(url); if (App.getConfig().logToConsole()) started = displayTimeTaken("Profile load", started); List<NameValuePair> inputs = new ArrayList<NameValuePair>(10); getInputs(page, inputs, null); setValue(inputs, "Motto", motto); setValue(inputs, "RealName", name); setValue(inputs, "Location", location); setValue(inputs, "Bio", bio); try { submitRequest(url, inputs); // This shouldn't happen - user was not redirected throw new ParserException(mContext, R.string.error_updating_profile); } catch (ClientProtocolException e) { // This is normal; user is redirected to his overview page } if (App.getConfig().logToConsole()) started = displayTimeTaken("Profile update", started); // Update local information ContentResolver cr = mContext.getContentResolver(); ContentValues cv = new ContentValues(10); cv.put(Profiles.MOTTO, motto); cv.put(Profiles.NAME, name); cv.put(Profiles.LOCATION, location); cv.put(Profiles.BIO, bio); Uri uri = ContentUris.withAppendedId(Profiles.CONTENT_URI, account.getId()); cr.update(uri, cv, null, null); account.setLastSummaryUpdate(System.currentTimeMillis()); account.save(Preferences.get(mContext)); cr.notifyChange(uri, null); } private FriendsOfFriend parseFriendsOfFriend(XboxLiveAccount account, String friendGamertag) throws IOException, ParserException { long started = System.currentTimeMillis(); FriendsOfFriend list = new FriendsOfFriend(mContext.getContentResolver()); String token = getVToken(String.format(URL_VTOKEN_FRIENDS, mLocale)); String url = String.format(URL_JSON_FOF_LIST, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); // Req. ver. token addValue(inputs, "__RequestVerificationToken", token); addValue(inputs, "gamertag", friendGamertag); String page = getResponse(url, inputs, true); JSONObject json = getXboxJsonObject(page); if (json == null) return list; JSONArray friends = json.optJSONArray("Friends"); if (friends == null) return list; ArrayList<Gamer> shared = new ArrayList<Gamer>(); ArrayList<Gamer> notYet = new ArrayList<Gamer>(); for (int i = 0, n = friends.length(); i < n; i++) { String gamertag; JSONObject friend = friends.optJSONObject(i); if (friend == null || (gamertag = friend.optString("GamerTag")) == null) continue; long lastSeen = parseTicks(friend.optString("LastSeen")); Gamer gamer = new Gamer(); gamer.Gamertag = gamertag; gamer.Gamerscore = friend.optInt("GamerScore", 0); gamer.IconUrl = friend.optString("LargeGamerTileUrl"); JSONObject titleInfo = friend.optJSONObject("TitleInfo"); if (titleInfo != null) { gamer.TitleIconUrl = getBoxArt(titleInfo.optInt("Id", 0), false); if (titleInfo.optInt("Id") == 0) { gamer.CurrentActivity = mContext.getString(R.string.online_status_unavailable); } else { gamer.CurrentActivity = mContext.getString(R.string.last_seen_playing_f, DateFormat.getDateInstance().format(lastSeen), titleInfo.optString("Name")); } } gamer.IsFriend = Friends.isFriend(mContext, account, gamertag); if (gamer.IsFriend) shared.add(gamer); else notYet.add(gamer); } if (App.getConfig().logToConsole()) started = displayTimeTaken("Friends of friend page loading", started); Gamer.Comparator cmp = new Gamer.Comparator(); Collections.sort(shared, cmp); Collections.sort(notYet, cmp); if (App.getConfig().logToConsole()) started = displayTimeTaken("Friends of friend page sorting", started); long id = 0; for (Gamer gamer : shared) { list.SharedFriends.addItem(id++, gamer.Gamertag, gamer.CurrentActivity, gamer.IconUrl, gamer.TitleIconUrl, gamer.TitleId, gamer.Gamerscore, gamer.IsFriend); } for (Gamer gamer : notYet) { list.NotYetFriends.addItem(id++, gamer.Gamertag, gamer.CurrentActivity, gamer.IconUrl, gamer.TitleIconUrl, gamer.TitleId, gamer.Gamerscore, gamer.IsFriend); } return list; } private RecentPlayers parseRecentPlayers(XboxLiveAccount account) throws IOException, ParserException { long started = System.currentTimeMillis(); RecentPlayers list = new RecentPlayers(mContext.getContentResolver()); String token = getVToken(String.format(URL_VTOKEN_FRIENDS, mLocale)); String url = String.format(URL_JSON_RECENT_LIST, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); // Req. ver. token addValue(inputs, "__RequestVerificationToken", token); JSONArray players = getXboxJsonArray(url, inputs); if (App.getConfig().logToConsole()) started = displayTimeTaken("Player list page fetch", started); if (players == null) throw new ParserException(mContext, R.string.error_player_retrieval); ArrayList<Gamer> notYet = new ArrayList<Gamer>(); for (int i = 0, n = players.length(); i < n; i++) { String gamertag; JSONObject player = players.optJSONObject(i); if (player == null || (gamertag = player.optString("GamerTag")) == null) continue; long lastSeen = parseTicks(player.optString("LastSeen")); Gamer gamer = new Gamer(); gamer.Gamertag = gamertag; gamer.Gamerscore = player.optInt("GamerScore", 0); gamer.IconUrl = player.optString("LargeGamerTileUrl"); JSONObject titleInfo = player.optJSONObject("TitleInfo"); if (titleInfo != null) { gamer.TitleIconUrl = getBoxArt(titleInfo.optInt("Id", 0), false); if (titleInfo.optInt("Id") == 0) { gamer.CurrentActivity = mContext.getString(R.string.online_status_unavailable); } else { gamer.CurrentActivity = mContext.getString(R.string.last_seen_playing_f, DateFormat.getDateInstance().format(lastSeen), titleInfo.optString("Name")); } } notYet.add(gamer); } if (App.getConfig().logToConsole()) started = displayTimeTaken("Recent players page loading", started); Gamer.Comparator cmp = new Gamer.Comparator(); Collections.sort(notYet, cmp); if (App.getConfig().logToConsole()) started = displayTimeTaken("Recent players page sorting", started); for (Gamer gamer : notYet) { list.Players.addItem(gamer.Gamertag, gamer.CurrentActivity, gamer.IconUrl, gamer.TitleIconUrl, gamer.TitleId, gamer.Gamerscore, gamer.IsFriend); } return list; } private void parseMessages(XboxLiveAccount account) throws IOException, ParserException { long started = System.currentTimeMillis(); String token = getVToken(String.format(URL_VTOKEN_MESSAGES, mLocale)); String url = String.format(URL_JSON_MESSAGE_LIST, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "__RequestVerificationToken", token); String page = getResponse(url, inputs, true); JSONObject data = getXboxJsonObject(page); if (data == null) throw new ParserException(mContext, R.string.error_message_retrieval); JSONArray messages = data.optJSONArray("Messages"); if (App.getConfig().logToConsole()) started = displayTimeTaken("Message page fetch", started); long updated = started; boolean changed = false; String uid; ContentResolver cr = mContext.getContentResolver(); Cursor c; ContentValues cv; List<ContentValues> newCvs = new ArrayList<ContentValues>(); final String[] columns = new String[] { Messages._ID, Messages.IS_READ }; for (int i = 0, n = messages.length(); i < n; i++) { JSONObject message = messages.optJSONObject(i); if (message == null || (uid = message.optString("Id")) == null) continue; int isRead = message.optBoolean("HasBeenRead") ? 1 : 0; c = cr.query(Messages.CONTENT_URI, columns, Messages.ACCOUNT_ID + "=" + account.getId() + " AND " + Messages.UID + "=" + uid, null, null); try { if (c != null && c.moveToFirst()) { String gamerpic = message.optString("GamerPic"); // Message already in system cv = new ContentValues(5); cv.put(Messages.IS_READ, isRead); cv.put(Messages.DELETE_MARKER, updated); cv.put(Messages.GAMERPIC, gamerpic); changed = true; cr.update(Messages.CONTENT_URI, cv, Messages._ID + "=" + c.getLong(0), null); } else { long sent = parseTicks(message.optString("SentTime")); String body = message.optString("Excerpt", ""); String sender = message.optString("From", ""); String gamerpic = message.optString("GamerPic"); int type = XboxLive.MESSAGE_TEXT; if (message.optBoolean("HasImage")) type = XboxLive.MESSAGE_OTHER; if (message.optBoolean("HasVoice")) type = XboxLive.MESSAGE_VOICE; // New message cv = new ContentValues(10); cv.put(Messages.ACCOUNT_ID, account.getId()); cv.put(Messages.SENDER, sender); cv.put(Messages.GAMERPIC, gamerpic); cv.put(Messages.UID, uid); cv.put(Messages.IS_READ, isRead); cv.put(Messages.IS_DIRTY, 1); cv.put(Messages.TYPE, type); cv.put(Messages.SENT, sent); cv.put(Messages.DELETE_MARKER, updated); cv.put(Messages.BODY, htmlDecode(body)); newCvs.add(cv); } } finally { if (c != null) c.close(); } } if (App.getConfig().logToConsole()) started = displayTimeTaken("Message list processing", started); if (newCvs.size() > 0) { changed = true; ContentValues[] cvs = new ContentValues[newCvs.size()]; newCvs.toArray(cvs); cr.bulkInsert(Messages.CONTENT_URI, cvs); if (App.getConfig().logToConsole()) displayTimeTaken("Message list insertion", started); } int deleted = cr.delete(Messages.CONTENT_URI, Messages.DELETE_MARKER + "!=" + updated + " AND " + Messages.ACCOUNT_ID + "=" + account.getId(), null); account.refresh(Preferences.get(mContext)); account.setLastMessageUpdate(System.currentTimeMillis()); account.save(Preferences.get(mContext)); if (changed || deleted > 0) cr.notifyChange(Messages.CONTENT_URI, null); } private void parseDeleteMessage(XboxLiveAccount account, long messageUid) throws IOException, ParserException { String token = getVToken(String.format(URL_VTOKEN_MESSAGES, mLocale)); String url = String.format(URL_JSON_DELETE_MESSAGE, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); // Message ID addValue(inputs, "msgID", messageUid); // Req. ver. token addValue(inputs, "__RequestVerificationToken", token); String page = getResponse(url, inputs, true); long started = System.currentTimeMillis(); JSONObject json = getJSONObject(page, false); try { if (!json.getBoolean("Success")) { if (App.getConfig().logToConsole()) App.logv("XboxLiveParser/parseDeleteMessage: Parser error: " + page); throw new ParserException( json.optString("Status", mContext.getString(R.string.message_could_not_be_deleted))); } } catch (JSONException e) { if (App.getConfig().logToConsole()) { App.logv("XboxLiveParser/parseDeleteMessage: JSON error: " + page); e.printStackTrace(); } } ContentResolver cr = mContext.getContentResolver(); int rows = cr.delete(Messages.CONTENT_URI, Messages.UID + "=" + messageUid + " AND " + Messages.ACCOUNT_ID + "=" + account.getId(), null); if (rows > 0) cr.notifyChange(Messages.CONTENT_URI, null); if (App.getConfig().logToConsole()) displayTimeTaken("Message deletion processing", started); } private void parseBlockMessage(XboxLiveAccount account, long messageUid) throws IOException, ParserException { String token = getVToken(String.format(URL_VTOKEN_MESSAGES, mLocale)); String url = String.format(URL_JSON_BLOCK_MESSAGE, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); // Message ID addValue(inputs, "msgID", messageUid); // Req. ver. token addValue(inputs, "__RequestVerificationToken", getVToken(token)); String page = getResponse(url, inputs, true); long started = System.currentTimeMillis(); JSONObject json = getJSONObject(page, false); try { if (!json.getBoolean("Success")) { if (App.getConfig().logToConsole()) App.logv("XboxLiveParser/parseBlockMessage: Parser error: " + page); throw new ParserException( json.optString("Status", mContext.getString(R.string.sender_not_blocked))); } } catch (JSONException e) { if (App.getConfig().logToConsole()) { App.logv("XboxLiveParser/parseDeleteMessage: JSON error: " + page); e.printStackTrace(); } } ContentResolver cr = mContext.getContentResolver(); int rows = cr.delete(Messages.CONTENT_URI, Messages.UID + "=" + messageUid + " AND " + Messages.ACCOUNT_ID + "=" + account.getId(), null); if (rows > 0) cr.notifyChange(Messages.CONTENT_URI, null); if (App.getConfig().logToConsole()) displayTimeTaken("Message deletion processing", started); } private void parseViewMessage(XboxLiveAccount account, long messageUid) throws IOException, ParserException { long started = System.currentTimeMillis(); String token = getVToken(String.format(URL_VTOKEN_MESSAGES, mLocale)); String url = String.format(URL_JSON_READ_MESSAGE, mLocale); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "msgID", messageUid); addValue(inputs, "__RequestVerificationToken", token); String page = getResponse(url, inputs, true); if (App.getConfig().logToConsole()) displayTimeTaken("Message page fetch", started); JSONObject json = getXboxJsonObject(page); String message; if ((json == null) || (message = json.optString("Text")) == null) throw new ParserException(mContext, R.string.error_json_parser_error); ContentResolver cr = mContext.getContentResolver(); ContentValues cv = new ContentValues(); if (!json.isNull("Text")) cv.put(Messages.BODY, htmlDecode(message)); cv.put(Messages.IS_DIRTY, 0); cv.put(Messages.IS_READ, 1); int rows = cr.update(Messages.CONTENT_URI, cv, Messages.UID + "=" + messageUid + " AND " + Messages.ACCOUNT_ID + "=" + account.getId(), null); if (rows < 1) throw new ParserException(mContext, R.string.message_not_found); cr.notifyChange(Messages.CONTENT_URI, null); if (App.getConfig().logToConsole()) displayTimeTaken("Message processing", started); } private ComparedAchievementInfo parseCompareAchievements(XboxLiveAccount account, String gamertag, String gameUid) throws IOException, ParserException { long started = System.currentTimeMillis(); String pageUrl = String.format(URL_COMPARE_ACHIEVEMENTS, mLocale, gameUid, URLEncoder.encode(gamertag, "UTF-8")); String page = getResponse(pageUrl); if (App.getConfig().logToConsole()) started = displayTimeTaken("Achievement compare page fetch", started); Matcher m; if (!(m = PATTERN_COMPARE_ACH_JSON.matcher(page)).find()) throw new ParserException(mContext, R.string.error_achieves_retrieval); JSONObject data = getJSONObject(m.group(1), false); JSONArray players = data.optJSONArray("Players"); if (players.length() < 2) throw new ParserException(mContext, R.string.error_achieves_retrieval); JSONObject you = players.optJSONObject(0); JSONObject me = players.optJSONObject(1); String yourGamertag = you.optString("Gamertag"); String myGamertag = me.optString("Gamertag"); ComparedAchievementInfo comparedAchieves = new ComparedAchievementInfo(mContext.getContentResolver()); comparedAchieves.yourAvatarIconUrl = you.optString("Gamerpic"); comparedAchieves.myAvatarIconUrl = me.optString("Gamerpic"); JSONArray achieves = data.optJSONArray("Achievements"); for (int i = 0, n = achieves.length(); i < n; i++) { JSONObject achieve = achieves.optJSONObject(i); if (achieve == null || (achieve.optString("Id")) == null) continue; JSONObject progRoot = achieve.optJSONObject("EarnDates"); if (progRoot == null) continue; JSONObject myProg = progRoot.optJSONObject(myGamertag); JSONObject yourProg = progRoot.optJSONObject(yourGamertag); //HashMap<String, Object> achieveMap = new HashMap<String, Object>(); int score = achieve.optInt("Score"); String uid = achieve.optString("Id"); String title; String description; String iconUrl; if (achieve.optBoolean("IsHidden")) { title = mContext.getString(R.string.secret_achieve_title); description = mContext.getString(R.string.secret_achieve_desc); iconUrl = URL_SECRET_ACHIEVE_TILE; } else { title = achieve.optString("Name"); description = achieve.optString("Description"); iconUrl = achieve.optString("TileUrl"); } int scoreEarned; long myAcquired = 0; int myIsLocked = 1; // My section scoreEarned = 0; if (myProg != null) { myIsLocked = 0; scoreEarned = score; if (!myProg.optBoolean("IsOffline")) myAcquired = parseTicks(myProg.optString("EarnedOn")); } comparedAchieves.myGamerscore += scoreEarned; // Your section scoreEarned = 0; long yourAcquired = 0; int yourIsLocked = 1; if (yourProg != null) { yourIsLocked = 0; scoreEarned = score; if (!yourProg.optBoolean("IsOffline")) yourAcquired = parseTicks(yourProg.optString("EarnedOn")); } comparedAchieves.yourGamerscore += scoreEarned; comparedAchieves.cursor.addItem(uid, title, description, score, myAcquired, myIsLocked, yourAcquired, yourIsLocked, iconUrl); } return comparedAchieves; } private ComparedGameInfo parseCompareGames(XboxLiveAccount account, String gamertag) throws IOException, ParserException { long started = System.currentTimeMillis(); String token = getVToken( String.format(URL_VTOKEN_COMPARE_GAMES, mLocale, URLEncoder.encode(gamertag, "UTF-8"))); String url = String.format(URL_JSON_COMPARE_GAMES, mLocale, URLEncoder.encode(gamertag, "UTF-8")); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "__RequestVerificationToken", token); String page = getResponse(url, inputs, true); JSONObject data = getXboxJsonObject(page); if (data == null) throw new ParserException(mContext, R.string.error_games_retrieval); if (App.getConfig().logToConsole()) started = displayTimeTaken("Game compare page fetch", started); ComparedGameInfo comparedGames = new ComparedGameInfo(mContext.getContentResolver()); JSONArray players = data.optJSONArray("Players"); if (players.length() < 2) throw new ParserException(mContext, R.string.error_games_retrieval); JSONObject you = players.optJSONObject(0); JSONObject me = players.optJSONObject(1); String yourGamertag = you.optString("Gamertag"); String myGamertag = me.optString("Gamertag"); comparedGames.yourAvatarIconUrl = you.optString("Gamerpic"); comparedGames.myAvatarIconUrl = me.optString("Gamerpic"); comparedGames.yourGamerscore = you.optInt("Gamerscore"); comparedGames.myGamerscore = me.optInt("Gamerscore"); JSONArray games = data.optJSONArray("Games"); for (int i = 0, n = games.length(); i < n; i++) { String uid; JSONObject game = games.optJSONObject(i); if (game == null || (uid = game.optString("Id")) == null) continue; JSONObject progRoot = game.optJSONObject("Progress"); if (progRoot == null) continue; JSONObject myProg = progRoot.optJSONObject(myGamertag); JSONObject yourProg = progRoot.optJSONObject(yourGamertag); if (myProg == null || yourProg == null) continue; int totalAch = game.optInt("PossibleAchievements", 0); if (account.isShowingApps() || totalAch > 0) { int totalScore = game.optInt("PossibleScore", 0); int myAch = myProg.optInt("Achievements", 0); int myScore = myProg.optInt("Score", 0); int yourAch = yourProg.optInt("Achievements", 0); int yourScore = yourProg.optInt("Score", 0); comparedGames.cursor.addItem(game.optString("Name"), uid, myAch, yourAch, totalAch, myScore, yourScore, totalScore, game.optString("BoxArt"), game.optString("Url")); } } return comparedGames; } private void parseFriendRequest(XboxLiveAccount account, String requestId, String gamertag) throws IOException, ParserException { long started = System.currentTimeMillis(); String token = getVToken( String.format(URL_VTOKEN_FRIEND_REQUEST, mLocale, URLEncoder.encode(gamertag, "UTF-8"))); List<NameValuePair> inputs = new ArrayList<NameValuePair>(3); addValue(inputs, "gamertag", gamertag); addValue(inputs, "__RequestVerificationToken", token); String url = String.format(URL_JSON_FRIEND_REQUEST, mLocale, requestId); String page = getResponse(url, inputs, true); if (App.getConfig().logToConsole()) displayTimeTaken("Friend manager page fetch", started); JSONObject json = getJSONObject(page, false); try { if (!json.getBoolean("Success")) { if (App.getConfig().logToConsole()) App.logv("XboxLiveParser/parseSendMessage: Parser error: " + page); throw new ParserException(mContext.getString(R.string.request_unsuccessful)); } } catch (JSONException e) { if (App.getConfig().logToConsole()) e.printStackTrace(); throw new ParserException(mContext, R.string.error_json_parser_error); } // Request friend list update parseFriends(account); saveSession(account); } private GameOverviewInfo parseGameOverview(String url) throws IOException, ParserException { String loadUrl = url; if (PATTERN_GAME_OVERVIEW_REDIRECTING_URL.matcher(url).find()) { // Redirecting URL; figure out where it's redirecting HttpParams p = mHttpClient.getParams(); try { p.setParameter("http.protocol.max-redirects", 1); HttpGet httpget = new HttpGet(url); HttpContext context = new BasicHttpContext(); HttpResponse response = mHttpClient.execute(httpget, context); HttpEntity entity = response.getEntity(); if (entity != null) entity.consumeContent(); HttpUriRequest request = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST); try { loadUrl = new URI(url).resolve(request.getURI()).toString(); } catch (URISyntaxException e) { if (App.getConfig().logToConsole()) e.printStackTrace(); } if (App.getConfig().logToConsole()) App.logv("Redirection URL determined to be " + loadUrl); } finally { p.setParameter("http.protocol.max-redirects", 0); } } String page = getResponse(loadUrl.concat("?NoSplash=1")); GameOverviewInfo overview = new GameOverviewInfo(); Matcher m; if (!(m = PATTERN_GAME_OVERVIEW_TITLE.matcher(page)).find()) throw new ParserException(mContext, R.string.error_no_details_available); overview.Title = htmlDecode(m.group(1)); if ((m = PATTERN_GAME_OVERVIEW_DESCRIPTION.matcher(page)).find()) overview.Description = htmlDecode(m.group(1)); if ((m = PATTERN_GAME_OVERVIEW_MANUAL.matcher(page)).find()) overview.ManualUrl = m.group(1); if ((m = PATTERN_GAME_OVERVIEW_ESRB.matcher(page)).find()) { overview.EsrbRatingDescription = htmlDecode(m.group(1)); overview.EsrbRatingIconUrl = m.group(2); } if ((m = PATTERN_GAME_OVERVIEW_BANNER.matcher(page)).find()) overview.BannerUrl = m.group(1); m = PATTERN_GAME_OVERVIEW_IMAGE.matcher(page); while (m.find()) overview.Screenshots.add(m.group(1)); return overview; } public LiveStatusInfo fetchServerStatus() throws IOException, ParserException { String url = String.format(URL_STATUS, mLocale); String page = getResponse(url); LiveStatusInfo info = new LiveStatusInfo(); Matcher m = PATTERN_STATUS_ITEM.matcher(page); while (m.find()) { String name = htmlDecode(m.group(1)); boolean isOk = "active".equals(m.group(2)); int status = isOk ? XboxLive.LIVE_STATUS_OK : XboxLive.LIVE_STATUS_ERROR; String statusText = m.group(3); info.addCategory(name, status, statusText); } return info; } private Object fetchParseable(XboxLiveAccount account, ParseableWithResult parseable) throws AuthenticationException, IOException, ParserException { boolean reauthenticated = false; Object result = null; do { if (!authenticate(account, true)) throw new AuthenticationException( mContext.getString(R.string.error_invalid_credentials_f, account.getEmailAddress())); try { result = parseable.doParse(); saveSession(account); break; } catch (ParserException e) { if (App.getConfig().logToConsole()) { App.logv("Unexpected exception"); e.printStackTrace(); } if (reauthenticated) throw e; } catch (ClientProtocolException e) { if (App.getConfig().logToConsole()) { App.logv("Unexpected exception"); e.printStackTrace(); } if (reauthenticated) throw e; } catch (IOException e) { if (App.getConfig().logToConsole()) { App.logv("Unexpected exception"); e.printStackTrace(); } if (reauthenticated) throw e; } if (App.getConfig().logToConsole()) App.logv("Re-authenticating"); reauthenticated = true; deleteSession(account); } while (true); return result; } private void fetchParseable(XboxLiveAccount account, Parseable parseable) throws AuthenticationException, IOException, ParserException { boolean reauthenticated = false; do { if (!authenticate(account, true)) throw new AuthenticationException( mContext.getString(R.string.error_invalid_credentials_f, account.getEmailAddress())); try { parseable.doParse(); saveSession(account); break; } catch (ParserException e) { if (App.getConfig().logToConsole()) { App.logv("Unexpected exception"); e.printStackTrace(); } if (reauthenticated) throw e; } catch (ClientProtocolException e) { if (App.getConfig().logToConsole()) { App.logv("Unexpected exception"); e.printStackTrace(); } if (reauthenticated) throw e; } catch (IOException e) { if (App.getConfig().logToConsole()) { App.logv("Unexpected exception"); e.printStackTrace(); } if (reauthenticated) throw e; } if (App.getConfig().logToConsole()) App.logv("Re-authenticating"); reauthenticated = true; deleteSession(account); } while (true); } public void fetchSummary(final XboxLiveAccount account) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseAccountSummary(account); } }); } public void fetchFriendSummary(final XboxLiveAccount account, final String gamertag) throws AuthenticationException, IOException, ParserException { // Do nothing - not much we need anymore } public void fetchGames(final XboxLiveAccount account) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseGames(account); } }); } public void fetchAchievements(final XboxLiveAccount account, final long gameId) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseAchievements(account, gameId); } }); } public void fetchFriends(final XboxLiveAccount account) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseFriends(account); } }); } public void fetchMessages(final XboxLiveAccount account) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseMessages(account); } }); } public void fetchMessage(final XboxLiveAccount account, final long messageId) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseViewMessage(account, messageId); } }); } public void fetchDeleteMessage(final XboxLiveAccount account, final long messageUid) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseDeleteMessage(account, messageUid); } }); } public void fetchBlockMessage(final XboxLiveAccount account, final long messageUid) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseBlockMessage(account, messageUid); } }); } public void fetchSendMessage(final XboxLiveAccount account, final String[] recipients, final String body) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseSendMessage(account, recipients, body); } }); } public void updateProfile(final XboxLiveAccount account, final String motto, final String name, final String location, final String bio) throws ParserException, IOException, AuthenticationException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseUpdateProfile(account, motto, name, location, bio); } }); } public void setBeacon(final XboxLiveAccount account, final long gameId, final String message) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseToggleBeacon(account, gameId, true, message); } }); } public void removeBeacon(final XboxLiveAccount account, final long gameId) throws AuthenticationException, IOException, ParserException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseToggleBeacon(account, gameId, false, null); } }); } private void fetchFriendRequest(final XboxLiveAccount account, final String requestId, final String gamertag) throws IOException, ParserException, AuthenticationException { fetchParseable(account, new Parseable() { @Override public void doParse() throws AuthenticationException, IOException, ParserException { parseFriendRequest(account, requestId, gamertag); } }); } public void addFriend(XboxLiveAccount account, String gamertag) throws AuthenticationException, IOException, ParserException { fetchFriendRequest(account, FRIEND_MANAGER_ADD, gamertag); } public void removeFriend(XboxLiveAccount account, String gamertag) throws AuthenticationException, IOException, ParserException { fetchFriendRequest(account, FRIEND_MANAGER_REMOVE, gamertag); } /* public void blockFriend(XboxLiveAccount account, String gamertag) throws AuthenticationException, IOException, ParserException { callFriendManager(account, FRIEND_MANAGER_BLOCK, gamertag); } */ public void cancelFriendRequest(XboxLiveAccount account, String gamertag) throws AuthenticationException, IOException, ParserException { fetchFriendRequest(account, FRIEND_MANAGER_CANCEL, gamertag); } public void acceptFriendRequest(XboxLiveAccount account, String gamertag) throws AuthenticationException, IOException, ParserException { fetchFriendRequest(account, FRIEND_MANAGER_ACCEPT, gamertag); } public void rejectFriendRequest(XboxLiveAccount account, String gamertag) throws AuthenticationException, IOException, ParserException { fetchFriendRequest(account, FRIEND_MANAGER_REJECT, gamertag); } public ComparedGameInfo fetchCompareGames(final XboxLiveAccount account, final String gamertag) throws AuthenticationException, IOException, ParserException { return (ComparedGameInfo) fetchParseable(account, new ParseableWithResult() { @Override public Object doParse() throws AuthenticationException, IOException, ParserException { return parseCompareGames(account, gamertag); } }); } public FriendsOfFriend fetchFriendsOfFriend(final XboxLiveAccount account, final String gamertag) throws AuthenticationException, IOException, ParserException { return (FriendsOfFriend) fetchParseable(account, new ParseableWithResult() { @Override public Object doParse() throws AuthenticationException, IOException, ParserException { return parseFriendsOfFriend(account, gamertag); } }); } public RecentPlayers fetchRecentPlayers(final XboxLiveAccount account) throws AuthenticationException, IOException, ParserException { return (RecentPlayers) fetchParseable(account, new ParseableWithResult() { @Override public Object doParse() throws AuthenticationException, IOException, ParserException { return parseRecentPlayers(account); } }); } public ComparedAchievementInfo fetchCompareAchievements(final XboxLiveAccount account, final String gamertag, final String gameUid) throws AuthenticationException, IOException, ParserException { return (ComparedAchievementInfo) fetchParseable(account, new ParseableWithResult() { @Override public Object doParse() throws AuthenticationException, IOException, ParserException { return parseCompareAchievements(account, gamertag, gameUid); } }); } public GameOverviewInfo fetchGameOverview(final XboxLiveAccount account, final String url) throws AuthenticationException, IOException, ParserException { return (GameOverviewInfo) fetchParseable(account, new ParseableWithResult() { @Override public Object doParse() throws AuthenticationException, IOException, ParserException { return parseGameOverview(url); } }); } @Override public ContentValues validateAccount(BasicAccount account) throws AuthenticationException, IOException, ParserException { if (!authenticate(account, true)) throw new AuthenticationException( mContext.getString(R.string.error_invalid_credentials_f, account.getLogonId())); ContentValues cv = parseSummaryData((XboxLiveAccount) account); cv.put(Profiles.ACCOUNT_ID, account.getId()); cv.put(Profiles.UUID, account.getUuid()); return cv; } @Override public void deleteAccount(BasicAccount account) { ContentResolver cr = mContext.getContentResolver(); long accountId = account.getId(); // Clear games & achievements Cursor c = cr.query(Games.CONTENT_URI, new String[] { Games._ID }, Games.ACCOUNT_ID + "=" + accountId, null, null); if (c != null) { StringBuffer buffer = new StringBuffer(); try { while (c.moveToNext()) { if (buffer.length() > 0) buffer.append(","); buffer.append(c.getLong(0)); } if (buffer.length() > 0) { // Clear achievements cr.delete(Achievements.CONTENT_URI, Achievements.GAME_ID + " IN (" + buffer.toString() + ")", null); } } catch (Exception e) { // Do nothing } finally { c.close(); } } try { // Clear games cr.delete(Games.CONTENT_URI, Games.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete friends cr.delete(Friends.CONTENT_URI, Friends.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete messages cr.delete(Messages.CONTENT_URI, Messages.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete sent messages cr.delete(SentMessages.CONTENT_URI, SentMessages.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete beacons cr.delete(Beacons.CONTENT_URI, Beacons.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete profiles cr.delete(Profiles.CONTENT_URI, Profiles.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete notify states cr.delete(NotifyStates.CONTENT_URI, Profiles.ACCOUNT_ID + "=" + accountId, null); } catch (Exception e) { // Do nothing } try { // Delete authenticated session deleteSession(account); } catch (Exception e) { // Do nothing } // Send notifications cr.notifyChange(Achievements.CONTENT_URI, null); cr.notifyChange(Games.CONTENT_URI, null); cr.notifyChange(Friends.CONTENT_URI, null); cr.notifyChange(Messages.CONTENT_URI, null); cr.notifyChange(Profiles.CONTENT_URI, null); cr.notifyChange(Beacons.CONTENT_URI, null); cr.notifyChange(SentMessages.CONTENT_URI, null); cr.notifyChange(NotifyStates.CONTENT_URI, null); } public void createAccount(BasicAccount account, ContentValues cv) { XboxLiveAccount xblAccount = (XboxLiveAccount) account; // Save changes to preferences xblAccount.setGamertag(cv.getAsString(Profiles.GAMERTAG)); xblAccount.setLastSummaryUpdate(System.currentTimeMillis()); xblAccount.setIconUrl(cv.getAsString(Profiles.ICON_URL)); xblAccount.setGoldStatus(cv.getAsBoolean(Profiles.IS_GOLD)); account.save(Preferences.get(mContext)); // Add profile to database ContentResolver cr = mContext.getContentResolver(); cr.insert(Profiles.CONTENT_URI, cv); cr.notifyChange(Profiles.CONTENT_URI, null); } private static final class Gamer implements Serializable { private static final long serialVersionUID = -7487842118929407021L; public String Gamertag; public String CurrentActivity; public String IconUrl; public String TitleIconUrl; public int Gamerscore; public String TitleId; public boolean IsFriend; public Gamer() { this.IsFriend = false; this.Gamertag = null; this.IconUrl = null; this.TitleIconUrl = null; this.CurrentActivity = null; this.Gamerscore = 0; this.TitleId = null; } public static class Comparator implements java.util.Comparator<Gamer> { @Override public int compare(Gamer object1, Gamer object2) { return object1.Gamertag.compareToIgnoreCase(object2.Gamertag); } } } }