package com.tomclaw.mandarin.im.icq; import android.content.ContentResolver; import android.os.Bundle; import android.text.TextUtils; import com.tomclaw.mandarin.BuildConfig; import com.tomclaw.mandarin.R; import com.tomclaw.mandarin.core.BuddyData; import com.tomclaw.mandarin.core.CoreService; import com.tomclaw.mandarin.core.GlobalProvider; import com.tomclaw.mandarin.core.GroupData; import com.tomclaw.mandarin.core.PreferenceHelper; import com.tomclaw.mandarin.core.QueryHelper; import com.tomclaw.mandarin.core.RequestHelper; import com.tomclaw.mandarin.core.Settings; import com.tomclaw.mandarin.core.exceptions.BuddyNotFoundException; import com.tomclaw.mandarin.im.StatusNotFoundException; import com.tomclaw.mandarin.im.StatusUtil; import com.tomclaw.mandarin.util.GsonSingleton; import com.tomclaw.mandarin.util.HttpParamsBuilder; import com.tomclaw.mandarin.util.HttpUtil; import com.tomclaw.mandarin.util.Logger; import com.tomclaw.mandarin.util.NameValuePair; import com.tomclaw.mandarin.util.StringUtil; import com.tomclaw.mandarin.util.UrlParser; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import static com.tomclaw.mandarin.im.icq.WimConstants.AIM_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.AIM_SID; import static com.tomclaw.mandarin.im.icq.WimConstants.AMP; import static com.tomclaw.mandarin.im.icq.WimConstants.ASSERT_CAPS; import static com.tomclaw.mandarin.im.icq.WimConstants.AUTORESPONSE; import static com.tomclaw.mandarin.im.icq.WimConstants.BUDDIES_ARRAY; import static com.tomclaw.mandarin.im.icq.WimConstants.BUDDYLIST; import static com.tomclaw.mandarin.im.icq.WimConstants.BUDDY_ICON; import static com.tomclaw.mandarin.im.icq.WimConstants.BUILD_NUMBER; import static com.tomclaw.mandarin.im.icq.WimConstants.CLIENT_LOGIN_URL; import static com.tomclaw.mandarin.im.icq.WimConstants.CLIENT_NAME; import static com.tomclaw.mandarin.im.icq.WimConstants.CLIENT_VERSION; import static com.tomclaw.mandarin.im.icq.WimConstants.DATA_OBJECT; import static com.tomclaw.mandarin.im.icq.WimConstants.DEVICE_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.DEV_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.DEV_ID_K; import static com.tomclaw.mandarin.im.icq.WimConstants.DISPLAY_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.EQUAL; import static com.tomclaw.mandarin.im.icq.WimConstants.EVENTS; import static com.tomclaw.mandarin.im.icq.WimConstants.EVENTS_ARRAY; import static com.tomclaw.mandarin.im.icq.WimConstants.EVENT_DATA_OBJECT; import static com.tomclaw.mandarin.im.icq.WimConstants.EXPIRES_IN; import static com.tomclaw.mandarin.im.icq.WimConstants.FETCH_BASE_URL; import static com.tomclaw.mandarin.im.icq.WimConstants.FORMAT; import static com.tomclaw.mandarin.im.icq.WimConstants.FRIENDLY; import static com.tomclaw.mandarin.im.icq.WimConstants.GROUPS_ARRAY; import static com.tomclaw.mandarin.im.icq.WimConstants.HOST_TIME; import static com.tomclaw.mandarin.im.icq.WimConstants.ID_FIELD; import static com.tomclaw.mandarin.im.icq.WimConstants.ID_TYPE; import static com.tomclaw.mandarin.im.icq.WimConstants.IM; import static com.tomclaw.mandarin.im.icq.WimConstants.IMF; import static com.tomclaw.mandarin.im.icq.WimConstants.IM_STATE; import static com.tomclaw.mandarin.im.icq.WimConstants.IM_STATES; import static com.tomclaw.mandarin.im.icq.WimConstants.IM_STATES_ARRAY; import static com.tomclaw.mandarin.im.icq.WimConstants.INCLUDE_PRESENCE_FIELDS; import static com.tomclaw.mandarin.im.icq.WimConstants.INVISIBLE; import static com.tomclaw.mandarin.im.icq.WimConstants.LANGUAGE; import static com.tomclaw.mandarin.im.icq.WimConstants.LAST_SEEN; import static com.tomclaw.mandarin.im.icq.WimConstants.LOGIN; import static com.tomclaw.mandarin.im.icq.WimConstants.LOGIN_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.MESSAGE; import static com.tomclaw.mandarin.im.icq.WimConstants.MINIMIZE_RESPONSE; import static com.tomclaw.mandarin.im.icq.WimConstants.MOBILE; import static com.tomclaw.mandarin.im.icq.WimConstants.MOOD_ICON; import static com.tomclaw.mandarin.im.icq.WimConstants.MOOD_TITLE; import static com.tomclaw.mandarin.im.icq.WimConstants.MSG_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.MY_INFO; import static com.tomclaw.mandarin.im.icq.WimConstants.NAME; import static com.tomclaw.mandarin.im.icq.WimConstants.OFFLINE_IM; import static com.tomclaw.mandarin.im.icq.WimConstants.PASSWORD; import static com.tomclaw.mandarin.im.icq.WimConstants.PEEK; import static com.tomclaw.mandarin.im.icq.WimConstants.POLL_TIMEOUT; import static com.tomclaw.mandarin.im.icq.WimConstants.POST_PREFIX; import static com.tomclaw.mandarin.im.icq.WimConstants.PRESENCE; import static com.tomclaw.mandarin.im.icq.WimConstants.RAW_MSG; import static com.tomclaw.mandarin.im.icq.WimConstants.RENEW_TOKEN; import static com.tomclaw.mandarin.im.icq.WimConstants.RENEW_TOKEN_URL; import static com.tomclaw.mandarin.im.icq.WimConstants.RESPONSE_OBJECT; import static com.tomclaw.mandarin.im.icq.WimConstants.R_PARAM; import static com.tomclaw.mandarin.im.icq.WimConstants.SEND_REQ_ID; import static com.tomclaw.mandarin.im.icq.WimConstants.SESSION_ENDED; import static com.tomclaw.mandarin.im.icq.WimConstants.SESSION_KEY; import static com.tomclaw.mandarin.im.icq.WimConstants.SESSION_SECRET; import static com.tomclaw.mandarin.im.icq.WimConstants.SESSION_TIMEOUT; import static com.tomclaw.mandarin.im.icq.WimConstants.SIG_SHA256; import static com.tomclaw.mandarin.im.icq.WimConstants.SOURCE_OBJECT; import static com.tomclaw.mandarin.im.icq.WimConstants.START_SESSION_URL; import static com.tomclaw.mandarin.im.icq.WimConstants.STATE; import static com.tomclaw.mandarin.im.icq.WimConstants.STATUS_CODE; import static com.tomclaw.mandarin.im.icq.WimConstants.STATUS_MSG; import static com.tomclaw.mandarin.im.icq.WimConstants.TIMEOUT; import static com.tomclaw.mandarin.im.icq.WimConstants.TIMESTAMP; import static com.tomclaw.mandarin.im.icq.WimConstants.TOKEN_A; import static com.tomclaw.mandarin.im.icq.WimConstants.TOKEN_OBJECT; import static com.tomclaw.mandarin.im.icq.WimConstants.TS; import static com.tomclaw.mandarin.im.icq.WimConstants.TYPE; import static com.tomclaw.mandarin.im.icq.WimConstants.TYPING; import static com.tomclaw.mandarin.im.icq.WimConstants.TYPING_STATUS; import static com.tomclaw.mandarin.im.icq.WimConstants.TYPING_STATUS_TYPE; import static com.tomclaw.mandarin.im.icq.WimConstants.URL_REGEX; import static com.tomclaw.mandarin.im.icq.WimConstants.USER_DATA_OBJECT; import static com.tomclaw.mandarin.im.icq.WimConstants.USER_TYPE; import static com.tomclaw.mandarin.im.icq.WimConstants.VIEW; import static com.tomclaw.mandarin.im.icq.WimConstants.WELL_KNOWN_URLS; /** * Created with IntelliJ IDEA. * User: solkin * Date: 6/9/13 * Time: 7:20 PM */ public class IcqSession { public static final String DEV_ID_VALUE = "ic12G5kB_856lXr1"; private static final String EVENTS_VALUE = "myInfo,presence,buddylist,typing,imState,im,sentIM,offlineIM,userAddedToBuddyList,service,buddyRegistered"; private static final String PRESENCE_FIELDS_VALUE = "userType,service,moodIcon,moodTitle,capabilities,aimId,displayId,friendly,state,buddyIcon,bigBuddyIcon,abPhones,smsNumber,statusMsg,seqNum,eventType,lastseen"; private static final String CLIENT_NAME_VALUE = "Mandarin%20Android"; private static final String CLIENT_VERSION_VALUE = BuildConfig.VERSION_NAME; private static final String BUILD_NUMBER_VALUE = String.valueOf(BuildConfig.VERSION_CODE); private static final String ASSERT_CAPS_VALUE = "4d616e646172696e20494d0003000000,094613544C7F11D18222444553540000"; private static final String DEVICE_ID_VALUE = "mandarin_device_id"; public static final int INTERNAL_ERROR = 1000; public static final int EXTERNAL_LOGIN_OK = 200; public static final int EXTERNAL_LOGIN_ERROR = 330; public static final int EXTERNAL_UNKNOWN = 0; public static final int EXTERNAL_SESSION_OK = 200; public static final int EXTERNAL_SESSION_RATE_LIMIT = 607; public static final int EXTERNAL_FETCH_OK = 200; public static final int EXTERNAL_FORBIDDEN = 403; private static final int TIMEOUT_SOCKET_ADDITION = (int) TimeUnit.SECONDS.toMillis(10); private static final int TIMEOUT_CONNECTION = (int) TimeUnit.MINUTES.toMillis(2); private static final int TIMEOUT_SESSION = (int) TimeUnit.DAYS.toMillis(14); private IcqAccountRoot icqAccountRoot; public IcqSession(IcqAccountRoot icqAccountRoot) { this.icqAccountRoot = icqAccountRoot; } public int clientLogin() { try { // Create and config connection URL url = new URL(CLIENT_LOGIN_URL); HttpURLConnection loginConnection = (HttpURLConnection) url.openConnection(); loginConnection.setConnectTimeout(TIMEOUT_CONNECTION); loginConnection.setReadTimeout(TIMEOUT_CONNECTION + TIMEOUT_SOCKET_ADDITION); Logger.log("timeout connection: " + TIMEOUT_CONNECTION); Logger.log("timeout session: " + TIMEOUT_SESSION); // Specifying login data. HttpParamsBuilder nameValuePairs = new HttpParamsBuilder() .appendParam(CLIENT_NAME, CLIENT_NAME_VALUE) .appendParam(CLIENT_VERSION, CLIENT_VERSION_VALUE) .appendParam(DEV_ID, DEV_ID_VALUE) .appendParam(FORMAT, WimConstants.FORMAT_JSON) .appendParam(ID_TYPE, "ICQ") .appendParam(PASSWORD, icqAccountRoot.getUserPassword()) .appendParam(LOGIN, icqAccountRoot.getUserId()); try { // Execute request. InputStream responseStream = HttpUtil.executePost(loginConnection, nameValuePairs.build()); String responseString = HttpUtil.streamToString(responseStream); responseStream.close(); Logger.log("client login = " + responseString); JSONObject jsonObject = new JSONObject(responseString); JSONObject responseObject = jsonObject.getJSONObject(RESPONSE_OBJECT); int statusCode = responseObject.getInt(STATUS_CODE); switch (statusCode) { case EXTERNAL_LOGIN_OK: { JSONObject dataObject = responseObject.getJSONObject(DATA_OBJECT); String login = dataObject.getString(LOGIN_ID); long hostTime = dataObject.getLong(HOST_TIME); String sessionSecret = dataObject.getString(SESSION_SECRET); JSONObject tokenObject = dataObject.getJSONObject(TOKEN_OBJECT); int expiresIn = tokenObject.getInt(EXPIRES_IN); String tokenA = tokenObject.getString(TOKEN_A); Logger.log("token a = " + tokenA); Logger.log("sessionSecret = " + sessionSecret); String sessionKey = StringUtil.getHmacSha256Base64(sessionSecret, icqAccountRoot.getUserPassword()); Logger.log("sessionKey = " + sessionKey); // Update client login result in database. icqAccountRoot.setClientLoginResult(login, tokenA, sessionKey, expiresIn, hostTime); return EXTERNAL_LOGIN_OK; } case EXTERNAL_LOGIN_ERROR: { return EXTERNAL_LOGIN_ERROR; } default: { return EXTERNAL_UNKNOWN; } } } finally { loginConnection.disconnect(); } } catch (Throwable e) { Logger.log("client login: " + e.getMessage()); return INTERNAL_ERROR; } } public int startSession() { try { URL url = new URL(START_SESSION_URL); HttpURLConnection startSessionConnection = (HttpURLConnection) url.openConnection(); startSessionConnection.setConnectTimeout(TIMEOUT_CONNECTION); startSessionConnection.setReadTimeout(TIMEOUT_CONNECTION + TIMEOUT_SOCKET_ADDITION); String statusValue = StatusUtil.getStatusValue(icqAccountRoot.getAccountType(), icqAccountRoot.getBaseStatusValue(icqAccountRoot.getStatusIndex())); // Add your data HttpParamsBuilder nameValuePairs = new HttpParamsBuilder() .appendParam(WimConstants.TOKEN_A, icqAccountRoot.getTokenA()) .appendParam(ASSERT_CAPS, ASSERT_CAPS_VALUE) .appendParam(BUILD_NUMBER, BUILD_NUMBER_VALUE) .appendParam(CLIENT_NAME, CLIENT_NAME_VALUE) .appendParam(CLIENT_VERSION, CLIENT_VERSION_VALUE) .appendParam(DEVICE_ID, DEVICE_ID_VALUE) .appendParam(EVENTS, EVENTS_VALUE) .appendParam(FORMAT, WimConstants.FORMAT_JSON) .appendParam(IMF, "plain") .appendParam(INCLUDE_PRESENCE_FIELDS, PRESENCE_FIELDS_VALUE) .appendParam(INVISIBLE, "false") .appendParam(DEV_ID_K, DEV_ID_VALUE) .appendParam(LANGUAGE, "ru-ru") .appendParam(MINIMIZE_RESPONSE, "0") .appendParam(MOBILE, "0") .appendParam(POLL_TIMEOUT, String.valueOf(TIMEOUT_CONNECTION)) .appendParam(RAW_MSG, "0") .appendParam(SESSION_TIMEOUT, String.valueOf(TIMEOUT_SESSION / 1000)) .appendParam(TS, String.valueOf(icqAccountRoot.getHostTime())) .appendParam(VIEW, statusValue); String hash = POST_PREFIX.concat(URLEncoder.encode(START_SESSION_URL, HttpUtil.UTF8_ENCODING)) .concat(AMP).concat(URLEncoder.encode(nameValuePairs.build(), HttpUtil.UTF8_ENCODING)); nameValuePairs.appendParam(SIG_SHA256, StringUtil.getHmacSha256Base64(hash, icqAccountRoot.getSessionKey())); Logger.log(nameValuePairs.build()); try { // Execute HTTP Post Request InputStream responseStream = HttpUtil.executePost(startSessionConnection, nameValuePairs.build()); String responseString = HttpUtil.streamToString(responseStream); responseStream.close(); Logger.log("start session = " + responseString); JSONObject jsonObject = new JSONObject(responseString); JSONObject responseObject = jsonObject.getJSONObject(RESPONSE_OBJECT); int statusCode = responseObject.getInt(STATUS_CODE); switch (statusCode) { case EXTERNAL_SESSION_OK: { JSONObject dataObject = responseObject.getJSONObject(DATA_OBJECT); String aimSid = dataObject.getString(AIM_SID); String fetchBaseUrl = dataObject.getString(FETCH_BASE_URL); // Parsing my info and well-known URL's to send requests. MyInfo myInfo = GsonSingleton.getInstance().fromJson( StringUtil.fixCyrillicSymbols(dataObject.getJSONObject(MY_INFO).toString()), MyInfo.class); WellKnownUrls wellKnownUrls = GsonSingleton.getInstance().fromJson( dataObject.getJSONObject(WELL_KNOWN_URLS).toString(), WellKnownUrls.class); // Update starts session result in database. icqAccountRoot.setStartSessionResult(aimSid, fetchBaseUrl, wellKnownUrls); // Request for status update before my info parsing to prevent status reset. icqAccountRoot.updateStatus(); // Update status info in my info to prevent status blinking. myInfo.setState(StatusUtil.getStatusValue(icqAccountRoot.getAccountType(), icqAccountRoot.getBaseStatusValue(icqAccountRoot.getStatusIndex()))); int moodStatusValue = icqAccountRoot.getMoodStatusValue(icqAccountRoot.getStatusIndex()); if (moodStatusValue == SetMoodRequest.STATUS_MOOD_RESET) { myInfo.setMoodIcon(null); } else { myInfo.setMoodIcon(StatusUtil.getStatusValue(icqAccountRoot.getAccountType(), icqAccountRoot.getMoodStatusValue(icqAccountRoot.getStatusIndex()))); } myInfo.setMoodTitle(icqAccountRoot.getStatusTitle()); myInfo.setStatusMsg(icqAccountRoot.getStatusMessage()); // Now we can update info. icqAccountRoot.setMyInfo(myInfo); return EXTERNAL_SESSION_OK; } case EXTERNAL_SESSION_RATE_LIMIT: { return EXTERNAL_SESSION_RATE_LIMIT; } // TODO: may be cases if ts incorrect. May be proceed too. default: { return EXTERNAL_UNKNOWN; } } } finally { startSessionConnection.disconnect(); } } catch (Throwable ex) { Logger.log("start session exception", ex); return INTERNAL_ERROR; } } public int renewToken() { try { HttpParamsBuilder builder = new HttpParamsBuilder() .appendParam(RENEW_TOKEN, "true"); String url = signRequest(HttpUtil.GET, RENEW_TOKEN_URL, false, builder); Logger.log("renew token request: " + url); HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); String response = HttpUtil.streamToString(HttpUtil.executeGet(connection)); Logger.log("renew token response: " + response); JSONObject jsonObject = new JSONObject(response); JSONObject responseObject = jsonObject.getJSONObject(RESPONSE_OBJECT); int statusCode = responseObject.getInt(STATUS_CODE); switch (statusCode) { case EXTERNAL_LOGIN_OK: { JSONObject dataObject = responseObject.getJSONObject(DATA_OBJECT); JSONObject userDataObject = dataObject.getJSONObject(USER_DATA_OBJECT); String login = userDataObject.getString(LOGIN_ID); Logger.log("renew token login = " + login); JSONObject tokenObject = dataObject.getJSONObject(TOKEN_OBJECT); int expiresIn = tokenObject.getInt(EXPIRES_IN); String tokenA = tokenObject.getString(TOKEN_A); Logger.log("renew token expires in = " + expiresIn); Logger.log("renew token token a = " + tokenA); // Update renew token result in database. icqAccountRoot.setRenewTokenResult(login, tokenA, expiresIn); break; } case EXTERNAL_FORBIDDEN: { return EXTERNAL_UNKNOWN; } default: { return EXTERNAL_UNKNOWN; } } return EXTERNAL_LOGIN_OK; } catch (Throwable ex) { Logger.log("renew token exception", ex); return INTERNAL_ERROR; } } /** * Start event fetching in verbal cycle. * * @return true if we are now in offline mode because of user decision. * false if our session is not accepted by the server. */ public boolean startEventsFetching() { Logger.log("start events fetching"); do { try { String fetchUrl = getFetchUrl(); URL url = new URL(fetchUrl); Logger.log("fetch url = " + fetchUrl); HttpURLConnection fetchEventConnection = (HttpURLConnection) url.openConnection(); fetchEventConnection.setConnectTimeout(TIMEOUT_CONNECTION); fetchEventConnection.setReadTimeout(TIMEOUT_CONNECTION + TIMEOUT_SOCKET_ADDITION); try { InputStream responseStream = HttpUtil.executeGet(fetchEventConnection); String responseString = HttpUtil.streamToString(responseStream); responseStream.close(); Logger.log("fetch events = " + responseString); JSONObject jsonObject = new JSONObject(responseString); JSONObject responseObject = jsonObject.getJSONObject(RESPONSE_OBJECT); int statusCode = responseObject.getInt(STATUS_CODE); switch (statusCode) { case EXTERNAL_FETCH_OK: { JSONObject dataObject = responseObject.getJSONObject(DATA_OBJECT); long hostTime = dataObject.optLong(TS); if (hostTime != 0) { // Update time and fetch base url. icqAccountRoot.setHostTime(hostTime); } String fetchBaseUrl = dataObject.optString(FETCH_BASE_URL); if (!TextUtils.isEmpty(fetchBaseUrl)) { icqAccountRoot.setFetchBaseUrl(fetchBaseUrl); } // Store account state. icqAccountRoot.updateAccount(); // Process events. JSONArray eventsArray = dataObject.getJSONArray(EVENTS_ARRAY); // Cycling all events. Logger.log("Cycling all events."); for (int c = 0; c < eventsArray.length(); c++) { JSONObject eventObject = eventsArray.getJSONObject(c); String eventType = eventObject.getString(TYPE); JSONObject eventData = eventObject.getJSONObject(EVENT_DATA_OBJECT); // Process event. processEvent(eventType, eventData); } break; } default: { // Something wend wrong. Let's reconnect if status is not offline. // Reset login and session data. Logger.log("Something wend wrong. Let's reconnect if status is not offline."); icqAccountRoot.resetSessionData(); icqAccountRoot.updateAccount(); return icqAccountRoot.getStatusIndex() == StatusUtil.STATUS_OFFLINE; } } } finally { fetchEventConnection.disconnect(); } Thread.sleep(TimeUnit.SECONDS.toMillis(2)); } catch (Throwable ex) { Logger.log("fetch events exception: " + ex.getMessage()); try { Thread.sleep(TimeUnit.SECONDS.toMillis(5)); } catch (InterruptedException ignored) { // We'll sleep while there is no network connection. } } } while (!icqAccountRoot.isOffline()); // Fetching until online. return true; } public String getFetchUrl() { return new StringBuilder() .append(icqAccountRoot.getFetchBaseUrl()) .append(AMP).append(FORMAT).append(EQUAL).append(WimConstants.FORMAT_JSON) .append(AMP).append(TIMEOUT).append(EQUAL).append(TIMEOUT_CONNECTION) .append(AMP).append(R_PARAM).append(EQUAL).append(System.currentTimeMillis()) .append(AMP).append(PEEK).append(EQUAL).append(0).toString(); } private void processEvent(String eventType, JSONObject eventData) { Logger.log("eventType = " + eventType + "; eventData = " + eventData.toString()); long processStartTime = System.currentTimeMillis(); switch (eventType) { case BUDDYLIST: try { ArrayList<GroupData> groupDatas = new ArrayList<>(); int accountDbId = icqAccountRoot.getAccountDbId(); String accountType = icqAccountRoot.getAccountType(); ContentResolver contentResolver = icqAccountRoot.getContentResolver(); JSONArray groupsArray = eventData.getJSONArray(GROUPS_ARRAY); for (int c = 0; c < groupsArray.length(); c++) { JSONObject groupObject = groupsArray.getJSONObject(c); String groupName = groupObject.getString(NAME); int groupId = groupObject.getInt(ID_FIELD); JSONArray buddiesArray = groupObject.getJSONArray(BUDDIES_ARRAY); ArrayList<BuddyData> buddyDatas = new ArrayList<>(); for (int i = 0; i < buddiesArray.length(); i++) { JSONObject buddyObject = buddiesArray.getJSONObject(i); String buddyId = buddyObject.getString(AIM_ID); String buddyNick = buddyObject.optString(FRIENDLY); if (TextUtils.isEmpty(buddyNick)) { buddyNick = buddyObject.optString(DISPLAY_ID, buddyId); } String buddyStatus = buddyObject.getString(STATE); String moodIcon = buddyObject.optString(MOOD_ICON); String statusMessage = buddyObject.optString(STATUS_MSG); String moodTitle = buddyObject.optString(MOOD_TITLE); int statusIndex = getStatusIndex(moodIcon, buddyStatus); String statusTitle = getStatusTitle(moodTitle, statusIndex); String buddyType = buddyObject.getString(USER_TYPE); String buddyIcon = buddyObject.optString(BUDDY_ICON); String bigBuddyIcon = buddyObject.optString(WimConstants.BIG_BUDDY_ICON); if (!TextUtils.isEmpty(bigBuddyIcon)) { buddyIcon = bigBuddyIcon; } long lastSeen = buddyObject.optLong(LAST_SEEN, -1); buddyDatas.add(new BuddyData(groupId, groupName, buddyId, buddyNick, statusIndex, statusTitle, statusMessage, buddyIcon, lastSeen)); } groupDatas.add(new GroupData(groupName, groupId, buddyDatas)); } // Prepare parameters to call update roster method. Bundle bundle = new Bundle(); bundle.putInt(GlobalProvider.KEY_ACCOUNT_DB_ID, accountDbId); bundle.putString(GlobalProvider.KEY_ACCOUNT_TYPE, accountType); bundle.putSerializable(GlobalProvider.KEY_GROUP_DATAS, groupDatas); contentResolver.call(Settings.BUDDY_RESOLVER_URI, GlobalProvider.METHOD_UPDATE_ROSTER, null, bundle); } catch (JSONException ex) { Logger.log("exception while parsing buddy list", ex); } break; case IM: case OFFLINE_IM: // TODO: offlineIM is differ! try { String messageText = eventData.getString(MESSAGE); String cookie = eventData.optString(MSG_ID); if (TextUtils.isEmpty(cookie)) { cookie = String.valueOf(System.currentTimeMillis()); } long messageTime = eventData.getLong(TIMESTAMP); String imf = eventData.getString(IMF); String autoResponse = eventData.getString(AUTORESPONSE); JSONObject sourceObject = eventData.optJSONObject(SOURCE_OBJECT); String buddyId; String buddyNick; int statusIndex; String statusTitle; String statusMessage = ""; String buddyIcon; long lastSeen = -1; if (sourceObject != null) { buddyId = sourceObject.getString(AIM_ID); buddyNick = sourceObject.optString(FRIENDLY); String buddyStatus = sourceObject.optString(STATE); String buddyType = sourceObject.optString(USER_TYPE); buddyIcon = sourceObject.optString(BUDDY_ICON); String bigBuddyIcon = sourceObject.optString(WimConstants.BIG_BUDDY_ICON); if (!TextUtils.isEmpty(bigBuddyIcon)) { buddyIcon = bigBuddyIcon; } lastSeen = sourceObject.optLong(LAST_SEEN, -1); statusIndex = getStatusIndex(null, buddyStatus); } else { buddyId = eventData.getString(AIM_ID); buddyNick = eventData.optString(FRIENDLY); buddyIcon = null; statusIndex = StatusUtil.STATUS_OFFLINE; } if (TextUtils.isEmpty(buddyNick)) { buddyNick = buddyId; } statusTitle = getStatusTitle(null, statusIndex); boolean isProcessed = false; do { try { Matcher matcher = URL_REGEX.matcher(messageText); while (matcher.find() && matcher.groupCount() == 1) { // TODO: also show message body. String url = matcher.group(); String fileId = matcher.group(1); int buddyDbId = QueryHelper.getBuddyDbId(icqAccountRoot.getContentResolver(), icqAccountRoot.getAccountDbId(), buddyId); String tag = cookie + ":" + url; RequestHelper.requestFileReceive(icqAccountRoot.getContentResolver(), buddyDbId, cookie, messageTime * 1000, fileId, url, messageText, tag); isProcessed = true; } if (!isProcessed) { QueryHelper.insertMessage(icqAccountRoot.getContentResolver(), PreferenceHelper.isCollapseMessages(icqAccountRoot.getContext()), icqAccountRoot.getAccountDbId(), buddyId, GlobalProvider.HISTORY_MESSAGE_TYPE_INCOMING, GlobalProvider.HISTORY_MESSAGE_STATE_UNDETERMINED, cookie, messageTime * 1000, messageText); } isProcessed = true; } catch (BuddyNotFoundException ignored) { if (PreferenceHelper.isIgnoreUnknown(icqAccountRoot.getContext())) { isProcessed = true; } else { String recycleString = icqAccountRoot.getResources().getString(R.string.recycle); QueryHelper.updateOrCreateBuddy(icqAccountRoot.getContentResolver(), icqAccountRoot.getAccountDbId(), icqAccountRoot.getAccountType(), System.currentTimeMillis(), GlobalProvider.GROUP_ID_RECYCLE, recycleString, buddyId, buddyNick, statusIndex, statusTitle, statusMessage, buddyIcon, lastSeen); } } // This will try to create buddy if such is not present // in roster and then retry message insertion. } while (!isProcessed); } catch (JSONException ex) { Logger.log("error while processing im - JSON exception", ex); } break; case IM_STATE: try { JSONArray imStatesArray = eventData.getJSONArray(IM_STATES_ARRAY); for (int c = 0; c < imStatesArray.length(); c++) { JSONObject imState = imStatesArray.getJSONObject(c); String state = imState.getString(STATE); String msgId = imState.getString(MSG_ID); String sendReqId = imState.optString(SEND_REQ_ID); for (int i = 0; i < IM_STATES.length; i++) { if (state.equals(IM_STATES[i])) { QueryHelper.updateMessageState(icqAccountRoot.getContentResolver(), i, sendReqId, msgId); break; } } } } catch (JSONException ex) { Logger.log("error while processing im state", ex); } break; case PRESENCE: try { String buddyId = eventData.getString(AIM_ID); String buddyStatus = eventData.getString(STATE); String moodIcon = eventData.optString(MOOD_ICON); String statusMessage = StringUtil.unescapeXml(eventData.optString(STATUS_MSG)); String moodTitle = StringUtil.unescapeXml(eventData.optString(MOOD_TITLE)); int statusIndex = getStatusIndex(moodIcon, buddyStatus); String statusTitle = getStatusTitle(moodTitle, statusIndex); String buddyIcon = eventData.optString(BUDDY_ICON); String bigBuddyIcon = eventData.optString(WimConstants.BIG_BUDDY_ICON); if (!TextUtils.isEmpty(bigBuddyIcon)) { buddyIcon = bigBuddyIcon; } long lastSeen = eventData.optLong(LAST_SEEN, -1); QueryHelper.modifyBuddyStatus(icqAccountRoot.getContentResolver(), icqAccountRoot.getAccountDbId(), buddyId, statusIndex, statusTitle, statusMessage, buddyIcon, lastSeen); } catch (JSONException ex) { Logger.log("error while processing presence - JSON exception", ex); } catch (BuddyNotFoundException ex) { Logger.log("error while processing presence - buddy not found"); } break; case TYPING: try { String buddyId = eventData.getString(AIM_ID); String typingStatus = eventData.getString(TYPING_STATUS); QueryHelper.modifyBuddyTyping(icqAccountRoot.getContentResolver(), icqAccountRoot.getAccountDbId(), buddyId, TextUtils.equals(typingStatus, TYPING_STATUS_TYPE)); } catch (Throwable ex) { Logger.log("error while processing typing", ex); } break; case MY_INFO: try { MyInfo myInfo = GsonSingleton.getInstance().fromJson( StringUtil.fixCyrillicSymbols(eventData.toString()), MyInfo.class); icqAccountRoot.setMyInfo(myInfo); } catch (Throwable ex) { Logger.log("error while processing my info", ex); } break; case SESSION_ENDED: icqAccountRoot.resetSessionData(); icqAccountRoot.carriedOff(); break; } Logger.log("processed in " + (System.currentTimeMillis() - processStartTime) + " ms."); } protected String getStatusTitle(String moodTitle, int statusIndex) { // Define status title. String statusTitle; if (TextUtils.isEmpty(moodTitle)) { // Default title for status index. statusTitle = StatusUtil.getStatusTitle(icqAccountRoot.getAccountType(), statusIndex); } else { // Buddy specified title. statusTitle = moodTitle; } return statusTitle; } protected int getStatusIndex(String moodIcon, String buddyStatus) { int statusIndex; // Checking for mood present. if (!TextUtils.isEmpty(moodIcon)) { try { return StatusUtil.getStatusIndex(icqAccountRoot.getAccountType(), parseMood(moodIcon)); } catch (StatusNotFoundException ignored) { } } try { statusIndex = StatusUtil.getStatusIndex(icqAccountRoot.getAccountType(), buddyStatus); } catch (StatusNotFoundException ex) { statusIndex = StatusUtil.STATUS_OFFLINE; } return statusIndex; } /** * Returns "id" parameter value from specified URL */ private static String getIdParam(String url) { URI uri = URI.create(url); for (NameValuePair param : UrlParser.parse(uri, "UTF-8")) { if (param.getName().equals("id")) { return param.getValue(); } } return ""; } /** * Parsing specified URL for "id" parameter, decoding it from UTF-8 byte array in HEX presentation */ public static String parseMood(String moodUrl) { if (moodUrl != null) { final String id = getIdParam(moodUrl); InputStream is = new InputStream() { int pos = 0; int length = id.length(); @Override public int read() throws IOException { if (pos == length) return -1; char c1 = id.charAt(pos++); char c2 = id.charAt(pos++); return (Character.digit(c1, 16) << 4) | Character.digit(c2, 16); } }; DataInputStream dis = new DataInputStream(is); try { return dis.readUTF(); } catch (IOException e) { e.printStackTrace(); } } return moodUrl; } public String signRequest(String method, String url, HttpParamsBuilder builder) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException { return signRequest(method, url, true, builder); } public String signRequest(String method, String url, boolean includeSession, HttpParamsBuilder builder) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException { builder.appendParam(WimConstants.TOKEN_A, icqAccountRoot.getTokenA()); if (includeSession) { builder.appendParam(WimConstants.AIM_SID, icqAccountRoot.getAimSid()); } builder.appendParam(WimConstants.FORMAT, WimConstants.FORMAT_JSON) .appendParam(WimConstants.DEV_ID_K, DEV_ID_VALUE) .appendParam(WimConstants.TS, String.valueOf(icqAccountRoot.getHostTime())); builder.sortParams(); String params = builder.build(); String hash = method.concat(WimConstants.AMP) .concat(StringUtil.urlEncode(url)).concat(WimConstants.AMP) .concat(StringUtil.urlEncode(params)); return url.concat(WimConstants.QUE).concat(params).concat(WimConstants.AMP) .concat(WimConstants.SIG_SHA256).concat(EQUAL) .concat(StringUtil.urlEncode(StringUtil.getHmacSha256Base64(hash, icqAccountRoot.getSessionKey()))); } }