package com.closedcircles.client; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.widget.Toast; import com.closedcircles.client.model.*; import com.closedcircles.client.activities.*; import com.closedcircles.client.adapters.*; import com.loopj.android.http.*; // singleton public class WebConnectionManager { private static WebConnectionManager mInstance = null; private Context mContext = null; private CirclesActivity mActivity = null; private Account mAccount = new Account(); private String mXSRF = new String(); private BroadcastReceiver mReceiver = null; private int mFailedUpdateCount = 0; private long mLocalData = (long) (Math.random() * 1000) + 1; private Handler mHandler = new Handler(); private Runnable mTimer = null; private int mReloadCount = 0; public final static int HISTORY_LENGTH = 10; private ProgressDialog mProgressDialog = null; private boolean mPause = true; public final static int MESSAGE_MAKE_READ_TIME = 1; // 1 second to read messages public final static int RESTART_UPDATES_TIME = 60; // 60 sec public final static int MAX_RELOAD_TOKEN_COUNT = 2; protected WebConnectionManager() { } public static WebConnectionManager get() { if (mInstance == null) mInstance = new WebConnectionManager(); return mInstance; } public Account account() { return mAccount; } public Activity activity() { return mActivity; } public void setup(CirclesActivity activity, Bundle saveState) { mActivity = activity; if (mContext == null) { // if first time call mContext = activity.getApplicationContext(); WebConnection.create(mContext); if (mReceiver == null) mReceiver = new ConnectivityChangeReceiver(mContext); IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiver(mReceiver, filter); } else mContext = activity.getApplicationContext(); //String str = saveState != null?"setup: saveState is not null": "setup: saveState is null"; //str += mAccount.getCircles().isEmpty() ? "circles are empty": "circles are not empty"; //Toast.makeText(mContext, str, Toast.LENGTH_LONG).show(); if (saveState != null ) mAccount.restoreState(saveState); } public String XSRF(){ return mXSRF; } public void setXSRF(String s){ mXSRF = s; } public void onCirclesActivityClosed() { mActivity = null; doStop(); } public boolean checkConnection() { ConnectivityManager conMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); if (conMgr == null) return false; NetworkInfo i = conMgr.getActiveNetworkInfo(); return i != null && i.isConnectedOrConnecting(); } public Context getContext() { return mContext; } public void reloadToken() { if (mContext == null ) return; boolean signOut = false; if ( mReloadCount >= MAX_RELOAD_TOKEN_COUNT ) { signOut = true; mReloadCount = 0; } else mReloadCount ++; //Toast.makeText(mContext, "reload Token", Toast.LENGTH_LONG).show(); mFailedUpdateCount = 0; Log.w(getClass().getName(), "reloadToken..."); if (checkConnection() && !mPause ) { // reload token only if we have connection if (mActivity != null) { mActivity.startLogin(signOut); } } else onConnectionLost(); } private void onConnectionLost() { if (mContext == null) return; mPause = true; // stop updates mFailedUpdateCount = 0; } public void doAuth(String token, String authType) { Log.w(getClass().getName(), "Doing auth with token..."); mAccount.clear(); mXSRF = ""; String authPath; RequestParams params; if (authType.equals(LoginActivity.AUTH_TYPE_GOOGLE)) { params = new RequestParams(WebConnection.PARAM_GOOGLE_ACCESS_TOKEN, token); params.put("token_type", "Bearer"); params.put("expires_in", "3600"); params.put("state", "googleauthnext%3D%252Fchat"); authPath = WebConnection.PATH_AUTH_GOOGLE; } else { params = new RequestParams(WebConnection.PARAM_ACCESS_TOKEN, token); authPath = WebConnection.PATH_AUTH; } WebConnection.get(authPath, params, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, org.apache.http.Header[] headers, byte[] responseBody) { authComplete(); } @Override public void onFailure(int statusCode, org.apache.http.Header[] headers, byte[] responseBody, java.lang.Throwable error) { Toast.makeText(mContext, "doAuth failed", Toast.LENGTH_LONG).show(); Log.e(getClass().getName(), "OnFailure!", error); reloadToken(); } }); } private void authComplete() { Log.w(getClass().getName(), "Auth complete..."); mProgressDialog = new ProgressDialog(mActivity); mProgressDialog.setMessage("Joining..."); mProgressDialog.setCancelable(false); mProgressDialog.show(); mXSRF = WebConnection.cookie("_xsrf"); RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_HISTORY_LENGTH, Integer.toString(HISTORY_LENGTH)); WebConnection.post(WebConnection.PATH_JOINROOM, params, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject obj) { if( mProgressDialog != null && mProgressDialog.isShowing() ) mProgressDialog.dismiss(); joinComplete(obj); } @Override public void onFailure(int statusCode, org.apache.http.Header[] headers, java.lang.String responseBody, java.lang.Throwable e) { if( mProgressDialog != null && mProgressDialog.isShowing() ) mProgressDialog.dismiss(); Log.e(getClass().getName(), "Join failed!", e); Toast.makeText(mContext, "join failed", Toast.LENGTH_LONG).show(); reloadToken(); } }); } private void joinComplete(JSONObject initial) { if (mAccount.getNotification() == -1) { // circles : [{circleId, name}] // name // usersettings // userid // notificationCursor <-- Global messages, outside of circles // currentCircle // - name // messages : [text, parentName, cookie, id, vislist: [], name, // parentUserId, parentId, userId, flags, time, type] // markers : [{"marker": 225625, "thread": 6794}, ...] // cursor // requests : [] // friends : [{"id":0,"profileLink":"http://www.facebook.com/sim0nsays","website":"http://sim0nsays.livejournal.com","status":0,"fullname":"Simon Kozlov","idFB":591568993,"name":"Simon"}] // id // serverVersion // pending long initial_circle = -1; try { mReloadCount = 0; // successful login - we can clear reload count JSONObject currentCircle = initial.getJSONObject("currentCircle"); initial_circle = currentCircle.getLong("id"); postInitialCircles(initial.getJSONArray("circles"), initial.getLong("notificationCursor"), initial.getString(WebConnection.PARAM_SERVER_VERSION)); postMessages(initial_circle, currentCircle.getLong("cursor"), currentCircle.getJSONArray("messages")); postUserInfo(initial.getJSONObject("userInfo")); postMarkers(initial_circle, currentCircle.getJSONArray("markers")); } catch (JSONException e) { e.printStackTrace(); Log.e(getClass().getName(), initial.toString()); } // Get other circles. for (int i = 0; i < mAccount.size(); ++i) { requestCircleState(mAccount.getCircle(i)); } } requestUpdates(false); } public void requestCircleState(Circle circle) { final Circle c = circle; Log.e(getClass().getName(), "Load circle " + c.getName() + ", id:" + c.getCursor()); if (c.getCursor() == -1) { RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_HISTORY_LENGTH, Integer.toString(HISTORY_LENGTH)); params.put(WebConnection.PARAM_CIRCLE, Long.toString(c.getId())); WebConnection.post(WebConnection.PATH_CIRCLESTATE, params, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject obj) { Log.e(getClass().getName(), "get state success:" + c.getName()); stateComplete(obj, c.getId()); } public void onFailure(int statusCode, org.apache.http.Header[] headers, java.lang.String responseBody, java.lang.Throwable e) { Log.e(getClass().getName(), "get state failure, error: " + responseBody + " circle: " + c.getName()); } }); } } private class ConnectivityChangeReceiver extends BroadcastReceiver { private Context mContext; public ConnectivityChangeReceiver(Context c) { mContext = c; } boolean checkConnection() { if (mContext == null) return false; ConnectivityManager conMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); if (conMgr == null) return false; NetworkInfo i = conMgr.getActiveNetworkInfo(); return i != null && i.isConnectedOrConnecting(); } @Override public void onReceive(Context context, Intent intent) { Log.i("NET", "Broadcast started"); boolean connectionExists = checkConnection(); if (connectionExists == mPause) { mPause = !connectionExists; if ( connectionExists) { // connection was restored -> request updates requestUpdates(true); } mPause = !connectionExists; } } } public void requestUpdates(boolean bRequestCircleState) { if ( !checkConnection() ) { // reload token only if we have connection onConnectionLost(); } if ( mPause || mXSRF.isEmpty() ) return; RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_NOTIFICATION_CURSOR, Long.toString(mAccount.getNotification())); params.put(WebConnection.PARAM_VERSION, mAccount.getVersion()); for (int i = 0; i < mAccount.size(); ++i) { Circle circle = mAccount.getCircle(i); if (circle.getCursor() > 0) { params.put(Long.toString(circle.getId()), Long.toString(circle.getCursor())); } else if ( bRequestCircleState ) requestCircleState(circle); // circle failed to load -> query circle state one more time } if ( mTimer != null ) mHandler.removeCallbacks(mTimer); mTimer = new Runnable(){ public void run(){ WebConnectionManager.get().requestUpdates(false); } }; mHandler.postDelayed(mTimer, 60*1000); // ask about updates after 1 minute WebConnection.post(WebConnection.PATH_UPDATES, params, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject obj) { mFailedUpdateCount = 0; updatesComplete(obj); } @Override public void onFailure(int statusCode, org.apache.http.Header[] headers, java.lang.String responseBody, java.lang.Throwable e) { Log.e(getClass().getName(), "OnFailure!", e); if ( e instanceof java.net.SocketTimeoutException ) { //Toast.makeText(mContext, "Update timeout", Toast.LENGTH_LONG).show(); requestUpdates(true); } else { String str = "update failed " + responseBody; //Toast.makeText(mContext, str, Toast.LENGTH_LONG).show(); mFailedUpdateCount ++; if ( mFailedUpdateCount > 2 ) { reloadToken(); } requestUpdates(true); } } }); } private void updatesComplete(JSONObject update) { try { if ( update.has("wrongVersion") && update.getBoolean("wrongVersion") ){ Log.i(getClass().getName(), "Server was restarted"); Toast.makeText(mContext, mActivity.getResources().getText(R.string.server_restart), Toast.LENGTH_LONG).show(); reloadToken(); // server restart return; } mAccount.setNotification(update.getLong("notificationCursor")); JSONArray msgs = update.getJSONArray("multimsgs"); for (int i = 0; i < msgs.length(); ++i) { JSONObject circle = msgs.getJSONObject(i); postMessages(circle.getLong("circleId"), circle.getLong("cursor"), circle.getJSONArray("messages")); } } catch (JSONException e) { e.printStackTrace(); // Fall through } requestUpdates(true); } public void fetchThreads(Circle circle) { for ( Map.Entry<Long, Circle.Thread> t: circle.getThreads().entrySet() ){ if ( t.getValue().size() > 0 && circle.getMessage( t.getValue().get(0)).getParent() != -1 ){ // first message is not root message -> we need to fetch complete thread fetchThread(circle.getId(), t.getKey()); } } } public void fetchThread(final long circleId, final long threadId) { if ( mPause || mXSRF.isEmpty() ) return; RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_CIRCLE, Long.toString(circleId)); params.put(WebConnection.PARAM_THREAD_ID, Long.toString(threadId)); WebConnection.post(WebConnection.PATH_FETCH_THREAD, params, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject obj) { fetchThreadComplete(circleId, threadId, obj); } @Override public void onFailure(int statusCode, org.apache.http.Header[] headers, java.lang.String responseBody, java.lang.Throwable e) { Log.v(getClass().getName(), "fetchThread failed"); } }); } private void fetchThreadComplete(long circleId, long threadId, JSONObject update) { try { postNewMessages(circleId, threadId, update.getJSONArray("messages"), update.getLong("marker")); } catch (JSONException e) { e.printStackTrace(); // Fall through } if ( mActivity != null ) { mActivity.updateCircles(); if ( mAccount.getSelectedCircle() != null && circleId == mAccount.getSelectedCircle().getId() ) { mActivity.updateThreads(); mActivity.updateMessages(false); } } } private void stateComplete(JSONObject state, long id) { try { postMessages(id, state.getLong("cursor"), state.getJSONArray("messages")); postMarkers(id, state.getJSONArray("markers")); } catch (JSONException e) { e.printStackTrace(); // Fall through } } void postInitialCircles(JSONArray circles, long notification, String version) { Log.w(getClass().getName(), "Posting circles: cursor " + notification + ", num " + circles.length()); mAccount.clear(); try { for (int i = 0; i < circles.length(); ++i) { JSONObject circle = circles.getJSONObject(i); long cur_id = circle.getLong("circleId"); Circle c = new Circle(cur_id, circle.getString("name")); mAccount.add(c); } } catch (JSONException e) { e.printStackTrace(); // Fall through } mAccount.setNotification(notification); mAccount.setVersion(version); if ( mActivity != null ) mActivity.getAdapter().notifyDataSetChanged(); } private void postMessages(long circleId, long cursor, JSONArray messages) { Log.w(getClass().getName(), "Posting: " + circleId + ", cursor " + cursor + ", num " + messages.length()); final Circle circle = mAccount.getCircleForId(circleId); if (circle == null) { Log.w(getClass().getName(), "postMessages: " + circleId + "cirlce is null"); return; } // if (circle.getCursor() >= cursor) { // We already have more messages. // Log.e(getClass().getName(), "Current cursor " + circle.getCursor() + ", new " + cursor); // return; // } List<Message> update = new ArrayList<Message>(messages.length()); Map<Long, Long> markers = new HashMap<Long, Long>(); long prev_id = -1; try { for (int i = 0; i < messages.length(); ++i) { JSONObject source = messages.getJSONObject(i); switch (source.getInt("type")) { case Message.TYPE_TEXT: long id = source.getLong("id"); if ( id == prev_id ) break; Message msg = Message.fromJson(source); update.add(msg); if (msg.getId() == mAccount.getNewMsgId()) { // we received message with selected message id -> make thread id selected mAccount.setSelectedThreadId(msg.getThread()); // make thread of new message selected } prev_id = id; break; case Message.TYPE_THREAD_READ: JSONObject cookie = source.getJSONObject("cookie"); markers.put(cookie.getLong("thread"), cookie.getLong("marker")); break; } } } catch (JSONException e) { e.printStackTrace(); // Fall through. } fetchThreads(circle); // check circle for non-complete threads circle.addAll(update); circle.setMarkers(markers, mAccount.getUserId()); circle.setCursor(cursor); if ( mActivity != null ) { mActivity.updateCircles(); if ( account().getSelectedCircle() != null && circle == account().getSelectedCircle() ) { mActivity.updateThreads(); mActivity.updateMessages(true); } } } private void postMarkers(long circleId, JSONArray markers) { Log.w(getClass().getName(), "Markers: " + circleId + ", num " + markers.length()); Map<Long, Long> threads = new HashMap<Long, Long>(); try { for (int i = 0; i < markers.length(); i++) { JSONObject elem = markers.getJSONObject(i); threads.put(elem.getLong("thread"), elem.getLong("marker")); } } catch (JSONException e) { e.printStackTrace(); // Fall through. } final Circle circle = mAccount.getCircleForId(circleId); if (circle == null) { return; } circle.setMarkers(threads, mAccount.getUserId()); //mAdapter.notifyCircleChanged(circleId); //mActivity.scrollCircleView(circleId); } private void postNewMessages(long circleId, long threadId, JSONArray messages, long marker) { Log.w(getClass().getName(), "Posting new messages: " + circleId + ", num " + messages.length()); final Circle circle = mAccount.getCircleForId(circleId); if (circle == null) { Log.w(getClass().getName(), "postNewMessages: " + circleId + "cirlce is null"); return; } List<Message> update = new ArrayList<Message>(messages.length()); Map<Long, Long> markers = new HashMap<Long, Long>(); markers.put(threadId, marker); long prev_id = -1; try { for (int i = 0; i < messages.length(); ++i) { JSONObject source = messages.getJSONObject(i); switch (source.getInt("type")) { case Message.TYPE_TEXT: long id = source.getLong("id"); if ( id == prev_id ) break; Message msg = Message.fromJson(source); update.add(msg); if (msg.getId() == mAccount.getNewMsgId()) { // we received message with selected message id -> make thread id selected mAccount.setSelectedThreadId(msg.getThread()); // make thread of new message selected } prev_id = id; break; case Message.TYPE_THREAD_READ: JSONObject cookie = source.getJSONObject("cookie"); markers.put(cookie.getLong("thread"), cookie.getLong("marker")); break; } } } catch (JSONException e) { e.printStackTrace(); // Fall through. } circle.addAll(update); circle.setMarkers(markers, mAccount.getUserId()); if ( mActivity != null ) { mActivity.updateCircles(); if ( circle == mAccount.getSelectedCircle() ) { mActivity.updateThreads(); mActivity.updateMessages(true); } } } private void postUserInfo(JSONObject userInfo) { try{ mAccount.setUserId(userInfo.getLong("id")); } catch (JSONException e) { e.printStackTrace(); Log.e(getClass().getName(), "Exception during getLong(id)"); } } public synchronized void doPause() { mPause = true; mFailedUpdateCount = 0; Log.w(getClass().getName(), "Pausing"); } public synchronized void doResume() { mPause = false; if (!mXSRF.isEmpty() && !mAccount.getCircles().isEmpty() ) { Log.w(getClass().getName(), "Resuming updates"); requestUpdates(true); } } public synchronized void doStop() { if ( mProgressDialog != null && mProgressDialog.isShowing() ) mProgressDialog.dismiss(); } public void sendMessage(String message) { if ( mContext == null || mAccount.getSelectedCircle() == null ) return; Log.w(getClass().getName(), "Sending " + message); RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_CIRCLE, Long.toString(mAccount.getSelectedCircle().getId())); params.put(WebConnection.PARAM_BODY, message); long parent = mAccount.getSelectedMsgId(); params.put(WebConnection.PARAM_PARENT_MESSAGE_ID, parent == -1 ? "null" : Long.toString(parent)); params.put(WebConnection.PARAM_TARGET_USER_ID, "null"); params.put(WebConnection.PARAM_VISIBLE_COUNT, "0"); params.put(WebConnection.PARAM_OPEN_ID, mAccount.getClosedMode()?"0":"1"); params.put(WebConnection.PARAM_LOCAL_DATA, Long.toString(mLocalData)); mLocalData++; WebConnection.post(WebConnection.PATH_NEW, params, new AsyncHttpResponseHandler() { @Override public void onSuccess(String content) { mAccount.setNewMsgId(Long.parseLong(content)); Log.i(getClass().getName(), "Response to SendMessage: " + content); //Toast.makeText(mContext, "response " + content , Toast.LENGTH_LONG).show(); } @Override public void onFailure(Throwable error, String content) { Log.e(getClass().getName(), "get updates failure " + error); // TODO: Notification? //Toast.makeText(mContext, "failure " + content , Toast.LENGTH_LONG).show(); } }); } public void editMessage(final long msg_id, String message) { Log.w(getClass().getName(), "Editing " + message); final Circle cirlce = mAccount.getSelectedCircle(); if ( cirlce == null || mActivity == null ) return; RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_CIRCLE, Long.toString(mAccount.getSelectedCircle().getId())); params.put(WebConnection.PARAM_BODY, message); params.put(WebConnection.PARAM_MSGID, Long.toString(msg_id)); WebConnection.post(WebConnection.PATH_EDIT, params, new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject obj) { Log.i(getClass().getName(), "editMessage suceeded"); try{ boolean succeeded = obj.getBoolean("result"); if ( !succeeded ){ Toast.makeText(mContext, mActivity.getResources().getText(R.string.delete_failed), Toast.LENGTH_LONG).show(); } else { cirlce.deleteMessage(msg_id); Circle.Thread t = mAccount.getSelectedThread(); if ( t == null ) return; if ( msg_id == mAccount.getSelectedMsgId() ) mAccount.setSelectedMsgId( cirlce.getMessage(t.get(0)).getId()); mActivity.updateMessages(false); } } catch(JSONException e){ Log.e(getClass().getName(), "JSON exception" + e.toString()); } } @Override public void onFailure(int statusCode, org.apache.http.Header[] headers, java.lang.String responseBody, java.lang.Throwable e) { Toast.makeText(mContext, "edit failed" + responseBody, Toast.LENGTH_LONG).show(); } }); } public void markAllRead() { Map<Long, Long> markers = null; Circle circle = mAccount.getSelectedCircle(); if ( circle == null ) { Toast.makeText(mContext, "Please select circle first", Toast.LENGTH_LONG).show(); return; } long cid = circle.getId(); if ( mActivity == null ) return; if ( mActivity.getPager().getCurrentItem() == CirclesActivity.FRAGMENT_MESSAGES ) { if ( mAccount.getSelectedThread() == null ) return; Circle.Thread t = mAccount.getSelectedThread(); markers = new HashMap<Long, Long>(); markers.put(t.getId(), circle.getMessage(t.getLast()).getId()); } else { markers = circle.lastMessages(); } JSONArray all = new JSONArray(); try { for (Map.Entry<Long, Long> entry: markers.entrySet()) { JSONObject marker = new JSONObject(); marker.put("circle", cid); marker.put("thread", entry.getKey()); marker.put("marker", entry.getValue()); all.put(marker); } } catch (JSONException e) { e.printStackTrace(); Log.e(getClass().getName(), "markAllRead failed"); return; } if (all.length() == 0) { Log.w(getClass().getName(), "No unread messages, doing nothing."); return; } RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_READ_STATUS, all.toString()); Log.w(getClass().getName(), "Sending " + all.toString()); WebConnection.post(WebConnection.PATH_SETREADMARKER, params, new AsyncHttpResponseHandler() { @Override public void onSuccess(String content) { Log.i(getClass().getName(), "Response to SetReadMarker: " + content); } @Override public void onFailure(Throwable error, String content) { Log.e(getClass().getName(), "set read marker failure " + error); // TODO: Notification? } }); } public void markRead(long threadId, long msgId) { Map<Long, Long> markers = null; Circle circle = mAccount.getSelectedCircle(); if ( circle == null ) { Toast.makeText(mContext, "Please select circle first", Toast.LENGTH_LONG).show(); return; } long cid = circle.getId(); Circle.Thread t = circle.getThreads().get(threadId); if ( t == null ) return; markers = new HashMap<Long, Long>(); markers.put(t.getId(), msgId); JSONArray all = new JSONArray(); try { for (Map.Entry<Long, Long> entry: markers.entrySet()) { JSONObject marker = new JSONObject(); marker.put("circle", cid); marker.put("thread", entry.getKey()); marker.put("marker", entry.getValue()); all.put(marker); } } catch (JSONException e) { e.printStackTrace(); Log.e(getClass().getName(), "markAllRead failed"); return; } if (all.length() == 0) { Log.w(getClass().getName(), "No unread messages, doing nothing."); return; } RequestParams params = new RequestParams(); params.put(WebConnection.PARAM_XSRF_TOKEN, mXSRF); params.put(WebConnection.PARAM_READ_STATUS, all.toString()); Log.w(getClass().getName(), "Sending " + all.toString()); WebConnection.post(WebConnection.PATH_SETREADMARKER, params, new AsyncHttpResponseHandler() { @Override public void onSuccess(String content) { Log.i(getClass().getName(), "Response to SetReadMarker: " + content); } @Override public void onFailure(Throwable error, String content) { Log.e(getClass().getName(), "set read marker failure " + error); // TODO: Notification? } }); }}