PageRenderTime 99ms CodeModel.GetById 3ms app.highlight 82ms RepoModel.GetById 1ms app.codeStats 1ms

/resources/ExDataset/codechanges/208/old/ChatController.java

https://bitbucket.org/tamnguyenthe/exassist_repo
Java | 2453 lines | 1777 code | 464 blank | 212 comment | 400 complexity | 0a10c9c7ee65359a08d3808dfeb153bc MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1package com.twofours.surespot.chat;
   2
   3import io.socket.IOAcknowledge;
   4import io.socket.IOCallback;
   5import io.socket.SocketIO;
   6import io.socket.SocketIOException;
   7
   8import java.io.BufferedInputStream;
   9import java.io.File;
  10import java.io.FileInputStream;
  11import java.io.FileNotFoundException;
  12import java.io.IOException;
  13import java.io.InputStream;
  14import java.io.InterruptedIOException;
  15import java.io.PipedInputStream;
  16import java.io.PipedOutputStream;
  17import java.net.URI;
  18import java.net.URISyntaxException;
  19import java.util.ArrayList;
  20import java.util.Date;
  21import java.util.HashMap;
  22import java.util.Iterator;
  23import java.util.List;
  24import java.util.Map.Entry;
  25import java.util.Timer;
  26import java.util.TimerTask;
  27import java.util.concurrent.ConcurrentLinkedQueue;
  28
  29import org.json.JSONArray;
  30import org.json.JSONException;
  31import org.json.JSONObject;
  32
  33import android.app.Activity;
  34import android.app.NotificationManager;
  35import android.content.BroadcastReceiver;
  36import android.content.Context;
  37import android.content.Intent;
  38import android.content.IntentFilter;
  39import android.graphics.Bitmap;
  40import android.net.ConnectivityManager;
  41import android.net.NetworkInfo;
  42import android.os.AsyncTask;
  43import android.os.Handler;
  44import android.os.Looper;
  45import android.support.v4.app.FragmentManager;
  46import android.support.v4.view.ViewPager;
  47import android.text.TextUtils;
  48import ch.boye.httpclientandroidlib.Header;
  49import ch.boye.httpclientandroidlib.HttpStatus;
  50import ch.boye.httpclientandroidlib.HttpVersion;
  51import ch.boye.httpclientandroidlib.StatusLine;
  52import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
  53import ch.boye.httpclientandroidlib.cookie.Cookie;
  54import ch.boye.httpclientandroidlib.impl.client.cache.HeapResource;
  55import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
  56import ch.boye.httpclientandroidlib.message.BasicHeader;
  57import ch.boye.httpclientandroidlib.message.BasicStatusLine;
  58
  59import com.actionbarsherlock.view.MenuItem;
  60import com.loopj.android.http.AsyncHttpResponseHandler;
  61import com.loopj.android.http.JsonHttpResponseHandler;
  62import com.twofours.surespot.R;
  63import com.twofours.surespot.StateController;
  64import com.twofours.surespot.StateController.FriendState;
  65import com.twofours.surespot.SurespotApplication;
  66import com.twofours.surespot.activities.MainActivity;
  67import com.twofours.surespot.common.SurespotConfiguration;
  68import com.twofours.surespot.common.SurespotConstants;
  69import com.twofours.surespot.common.SurespotLog;
  70import com.twofours.surespot.common.Utils;
  71import com.twofours.surespot.encryption.EncryptionController;
  72import com.twofours.surespot.friends.AutoInviteData;
  73import com.twofours.surespot.friends.Friend;
  74import com.twofours.surespot.friends.FriendAdapter;
  75import com.twofours.surespot.identity.IdentityController;
  76import com.twofours.surespot.images.MessageImageDownloader;
  77import com.twofours.surespot.network.IAsyncCallback;
  78import com.twofours.surespot.network.IAsyncCallbackTuple;
  79import com.twofours.surespot.network.NetworkController;
  80import com.viewpagerindicator.TitlePageIndicator;
  81
  82public class ChatController {
  83
  84	private static final String TAG = "ChatController";
  85	private static final int STATE_CONNECTING = 0;
  86	private static final int STATE_CONNECTED = 1;
  87	private static final int STATE_DISCONNECTED = 2;
  88
  89	private static final int MAX_RETRIES = 16;
  90
  91	private final StatusLine mImageStatusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "");
  92	private SocketIO socket;
  93	private int mRetries = 0;
  94	private Timer mBackgroundTimer;
  95
  96	private IOCallback mSocketCallback;
  97
  98	private ConcurrentLinkedQueue<SurespotMessage> mSendBuffer = new ConcurrentLinkedQueue<SurespotMessage>();
  99	private ConcurrentLinkedQueue<SurespotMessage> mResendBuffer = new ConcurrentLinkedQueue<SurespotMessage>();
 100
 101	private int mConnectionState;
 102	private boolean mOnWifi;
 103	private NotificationManager mNotificationManager;
 104	private BroadcastReceiver mConnectivityReceiver;
 105	private HashMap<String, ChatAdapter> mChatAdapters;
 106	private HashMap<String, Integer> mEarliestMessage;
 107
 108	private FriendAdapter mFriendAdapter;
 109	private ChatPagerAdapter mChatPagerAdapter;
 110	private ViewPager mViewPager;
 111	private TitlePageIndicator mIndicator;
 112	private FragmentManager mFragmentManager;
 113	private int mLatestUserControlId;
 114	private ArrayList<MenuItem> mMenuItems;
 115	private HashMap<String, LatestIdPair> mPreConnectIds;
 116
 117	private static String mCurrentChat;
 118	private static boolean mPaused = true;
 119	private NetworkController mNetworkController;
 120
 121	private Context mContext;
 122	public static final int MODE_NORMAL = 0;
 123	public static final int MODE_SELECT = 1;
 124
 125	private int mMode = MODE_NORMAL;
 126
 127	private IAsyncCallbackTuple<String, Boolean> mCallback401;
 128	private IAsyncCallback<Boolean> mProgressCallback;
 129	private IAsyncCallback<Void> mSendIntentCallback;
 130	private IAsyncCallback<Friend> mTabShowingCallback;
 131	private AutoInviteData mAutoInviteData;
 132
 133	public ChatController(Context context, NetworkController networkController, FragmentManager fm, IAsyncCallbackTuple<String, Boolean> m401Handler,
 134			IAsyncCallback<Boolean> progressCallback, IAsyncCallback<Void> sendIntentCallback, IAsyncCallback<Friend> tabShowingCallback) {
 135		SurespotLog.v(TAG, "constructor: " + this);
 136		mContext = context;
 137		mNetworkController = networkController;
 138
 139		mCallback401 = m401Handler;
 140		mProgressCallback = progressCallback;
 141		mSendIntentCallback = sendIntentCallback;
 142
 143		mTabShowingCallback = tabShowingCallback;
 144		mEarliestMessage = new HashMap<String, Integer>();
 145		mChatAdapters = new HashMap<String, ChatAdapter>();
 146		mFriendAdapter = new FriendAdapter(mContext);
 147		mPreConnectIds = new HashMap<String, ChatController.LatestIdPair>();
 148		loadState();
 149
 150		mFragmentManager = fm;
 151		mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 152
 153		setOnWifi();
 154
 155		// mViewPager.setOffscreenPageLimit(2);
 156
 157		mSocketCallback = new IOCallback() {
 158
 159			@Override
 160			public void onMessage(JSONObject json, IOAcknowledge ack) {
 161				try {
 162					SurespotLog.v(TAG, "JSON Server said: %s", json.toString(2));
 163
 164				}
 165				catch (JSONException e) {
 166					SurespotLog.w(TAG, "onMessage", e);
 167				}
 168			}
 169
 170			@Override
 171			public void onMessage(String data, IOAcknowledge ack) {
 172				SurespotLog.v(TAG, "Server said: %s", data);
 173			}
 174
 175			@Override
 176			public synchronized void onError(SocketIOException socketIOException) {
 177				// socket.io returns 403 for can't login
 178				if (socketIOException.getHttpStatus() == 403) {
 179					socket = null;
 180					logout();
 181					mCallback401.handleResponse(mContext.getString(R.string.could_not_login_to_server), false);
 182					return;
 183				}
 184
 185				SurespotLog.i(TAG, socketIOException, "an Error occured, attempting reconnect with exponential backoff, retries: %d", mRetries);
 186
 187				setOnWifi();
 188				// kick off another task
 189				if (mRetries < MAX_RETRIES) {
 190
 191					if (mReconnectTask != null) {
 192						mReconnectTask.cancel();
 193					}
 194
 195					int timerInterval = (int) (Math.pow(2, mRetries++) * 1000);
 196					SurespotLog.v(TAG, "Starting another task in: " + timerInterval);
 197
 198					mReconnectTask = new ReconnectTask();
 199					if (mBackgroundTimer == null) {
 200						mBackgroundTimer = new Timer("backgroundTimer");
 201					}
 202					mBackgroundTimer.schedule(mReconnectTask, timerInterval);
 203				}
 204				else {
 205					// TODO tell user
 206					SurespotLog.i(TAG, "Socket.io reconnect retries exhausted, giving up.");
 207					mCallback401.handleResponse(mContext.getString(R.string.could_not_connect_to_server), true);
 208				}
 209			}
 210
 211			@Override
 212			public void onDisconnect() {
 213				SurespotLog.v(TAG, "Connection terminated.");
 214				// socket = null;
 215			}
 216
 217			@Override
 218			public void onConnect() {
 219				SurespotLog.v(TAG, "socket.io connection established");
 220				setState(STATE_CONNECTED);
 221				setOnWifi();
 222				mRetries = 0;
 223
 224				if (mBackgroundTimer != null) {
 225					mBackgroundTimer.cancel();
 226					mBackgroundTimer = null;
 227				}
 228
 229				if (mReconnectTask != null && mReconnectTask.cancel()) {
 230					SurespotLog.v(TAG, "Cancelled reconnect timer.");
 231					mReconnectTask = null;
 232				}
 233
 234				connected();
 235
 236			}
 237
 238			@Override
 239			public void on(String event, IOAcknowledge ack, Object... args) {
 240
 241				SurespotLog.v(TAG, "Server triggered event '" + event + "'");
 242				if (event.equals("control")) {
 243					try {
 244						SurespotControlMessage message = SurespotControlMessage.toSurespotControlMessage(new JSONObject((String) args[0]));
 245						handleControlMessage(null, message, true, false);
 246					}
 247					catch (JSONException e) {
 248						SurespotLog.w(TAG, "on control", e);
 249					}
 250				}
 251				else
 252					if (event.equals("message")) {
 253						try {
 254							JSONObject jsonMessage = new JSONObject((String) args[0]);
 255							SurespotLog.v(TAG, "received message: " + jsonMessage.toString());
 256							SurespotMessage message = SurespotMessage.toSurespotMessage(jsonMessage);
 257							handleMessage(message);
 258							checkAndSendNextMessage(message);
 259
 260							// see if we have deletes
 261							String sDeleteControlMessages = jsonMessage.optString("deleteControlMessages", null);
 262							if (sDeleteControlMessages != null) {
 263								JSONArray deleteControlMessages = new JSONArray(sDeleteControlMessages);
 264
 265								if (deleteControlMessages.length() > 0) {
 266									for (int i = 0; i < deleteControlMessages.length(); i++) {
 267										try {
 268											SurespotControlMessage dMessage = SurespotControlMessage.toSurespotControlMessage(new JSONObject(
 269													deleteControlMessages.getString(i)));
 270											handleControlMessage(null, dMessage, true, false);
 271										}
 272										catch (JSONException e) {
 273											SurespotLog.w(TAG, "on control", e);
 274										}
 275									}
 276								}
 277
 278							}
 279
 280						}
 281						catch (JSONException e) {
 282							SurespotLog.w(TAG, "on message", e);
 283						}
 284
 285					}
 286					else
 287						if (event.equals("messageError")) {
 288							try {
 289								JSONObject jsonMessage = (JSONObject) args[0];
 290								SurespotLog.v(TAG, "received messageError: " + jsonMessage.toString());
 291								SurespotErrorMessage errorMessage = SurespotErrorMessage.toSurespotErrorMessage(jsonMessage);
 292								handleErrorMessage(errorMessage);
 293
 294							}
 295							catch (JSONException e) {
 296								SurespotLog.w(TAG, "on messageError", e);
 297							}
 298						}
 299			}
 300
 301		};
 302
 303		mConnectivityReceiver = new BroadcastReceiver() {
 304
 305			@Override
 306			public void onReceive(Context context, Intent intent) {
 307				SurespotLog.v(TAG, "Connectivity Action");
 308				ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
 309				NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
 310				if (networkInfo != null) {
 311					SurespotLog.v(TAG, "isconnected: " + networkInfo.isConnected());
 312					SurespotLog.v(TAG, "failover: " + networkInfo.isFailover());
 313					SurespotLog.v(TAG, "reason: " + networkInfo.getReason());
 314					SurespotLog.v(TAG, "type: " + networkInfo.getTypeName());
 315
 316					// if it's not a failover and wifi is now active then initiate reconnect
 317					if (!networkInfo.isFailover() && (networkInfo.getType() == ConnectivityManager.TYPE_WIFI && networkInfo.isConnected())) {
 318						synchronized (ChatController.this) {
 319							// if we're not connecting, connect
 320							if (getState() != STATE_CONNECTING && !mOnWifi) {
 321
 322								SurespotLog.v(TAG, "Network switch, Reconnecting...");
 323
 324								setState(STATE_CONNECTING);
 325
 326								mOnWifi = true;
 327								disconnect();
 328								connect();
 329							}
 330						}
 331					}
 332				}
 333				else {
 334					SurespotLog.v(TAG, "networkinfo null");
 335				}
 336			}
 337		};
 338
 339	}
 340
 341	// this has to be done outside of the contructor as it creates fragments, which need chat controller instance
 342	public void init(ViewPager viewPager, TitlePageIndicator pageIndicator, ArrayList<MenuItem> menuItems, AutoInviteData autoInviteData) {
 343		mChatPagerAdapter = new ChatPagerAdapter(mContext, mFragmentManager);
 344		mMenuItems = menuItems;
 345		mAutoInviteData = autoInviteData;
 346
 347		mViewPager = viewPager;
 348		mViewPager.setAdapter(mChatPagerAdapter);
 349		mIndicator = pageIndicator;
 350		mIndicator.setViewPager(mViewPager);
 351
 352		mIndicator.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
 353			@Override
 354			public void onPageSelected(int position) {
 355				if (mChatPagerAdapter != null) {
 356					SurespotLog.v(TAG, "onPageSelected, position: " + position);
 357					String name = mChatPagerAdapter.getChatName(position);
 358					setCurrentChat(name);
 359				}
 360
 361			}
 362		});
 363		mChatPagerAdapter.setChatNames(mFriendAdapter.getActiveChats());
 364		onResume();
 365	}
 366
 367	private void connect() {
 368		SurespotLog.v(TAG, "connect, socket: " + socket + ", connected: " + (socket != null ? socket.isConnected() : false) + ", state: " + mConnectionState);
 369
 370		// copy the latest ids so that we don't miss any if we receive new messages during the time we request messages and when the
 371		// connection completes (if they
 372		// are received out of order for some reason)
 373		//
 374		mPreConnectIds.clear();
 375		for (Entry<String, ChatAdapter> entry : mChatAdapters.entrySet()) {
 376			String username = entry.getKey();
 377			LatestIdPair idPair = new LatestIdPair();
 378			idPair.latestMessageId = getLatestMessageId(username);
 379			idPair.latestControlMessageId = getLatestMessageControlId(username);
 380			SurespotLog.v(TAG, "setting preconnectids for: " + username + ", latest message id:  " + idPair.latestMessageId + ", latestcontrolid: "
 381					+ idPair.latestControlMessageId);
 382			mPreConnectIds.put(username, idPair);
 383
 384		}
 385
 386		Cookie cookie = IdentityController.getCookie();
 387
 388		if (cookie == null) {
 389			return;
 390		}
 391
 392		try {
 393			HashMap<String, String> headers = new HashMap<String, String>();
 394			headers.put("cookie", cookie.getName() + "=" + cookie.getValue());
 395			socket = new SocketIO(SurespotConfiguration.getBaseUrl(), headers);
 396			socket.connect(mSocketCallback);
 397		}
 398		catch (Exception e) {
 399
 400			SurespotLog.w(TAG, "connect", e);
 401		}
 402
 403	}
 404
 405	private void disconnect() {
 406		SurespotLog.v(TAG, "disconnect.");
 407		setState(STATE_DISCONNECTED);
 408
 409		if (socket != null) {
 410			socket.disconnect();
 411			socket = null;
 412		}
 413
 414	}
 415
 416	private void connected() {
 417
 418		getFriendsAndIds();
 419		resendMessages();
 420
 421		// if we need to invite someone then do it
 422		if (mAutoInviteData != null) {
 423			if (mFriendAdapter.getFriend(mAutoInviteData.getUsername()) == null) {
 424				mNetworkController.invite(mAutoInviteData.getUsername(), mAutoInviteData.getSource(), new AsyncHttpResponseHandler() {
 425					@Override
 426					public void onSuccess(int statusCode, String arg0) {
 427						getFriendAdapter().addFriendInvited(mAutoInviteData.getUsername());
 428						mAutoInviteData = null;
 429
 430					}
 431				});
 432			}
 433			else {
 434				Utils.makeToast(mContext, mContext.getString(R.string.autoinvite_user_exists, mAutoInviteData.getUsername()));
 435				mAutoInviteData = null;
 436			}
 437
 438		}
 439
 440	}
 441
 442	private void resendMessages() {
 443		// get the resend messages
 444		SurespotMessage[] resendMessages = getResendMessages();
 445		JSONArray sMessageList = new JSONArray();
 446
 447		for (int i = 0; i < resendMessages.length; i++) {
 448			SurespotMessage message = resendMessages[i];
 449
 450			// if it has an id don't send it again
 451			if (message.getId() != null) {
 452				mResendBuffer.remove(message);
 453				continue;
 454			}
 455
 456			// set the last received id so the server knows which messages to check
 457			String otherUser = message.getOtherUser();
 458
 459			// String username = message.getFrom();
 460			Integer lastMessageID = 0;
 461			// ideally get the last id from the fragment's chat adapter
 462			ChatAdapter chatAdapter = mChatAdapters.get(otherUser);
 463			if (chatAdapter != null) {
 464				SurespotMessage lastMessage = chatAdapter.getLastMessageWithId();
 465				if (lastMessage != null) {
 466					lastMessageID = lastMessage.getId();
 467				}
 468			}
 469
 470			// failing that use the last viewed id
 471			if (lastMessageID == null) {
 472				mFriendAdapter.getFriend(otherUser).getLastViewedMessageId();
 473			}
 474
 475			SurespotLog.v(TAG, "setting resendId, otheruser: " + otherUser + ", id: " + lastMessageID);
 476			message.setResendId(lastMessageID);
 477
 478			// String sMessage = message.toJSONObject().toString();
 479			sMessageList.put(message.toJSONObject());
 480
 481			// enqueueMessage(message);
 482			// sendMessages();
 483
 484		}
 485
 486		socket.send(sMessageList.toString());
 487	}
 488
 489	private void setOnWifi() {
 490		// get the initial state...sometimes when the app starts it says "hey i'm on wifi" which creates a reconnect
 491		ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
 492		NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
 493		if (networkInfo != null) {
 494			mOnWifi = (networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
 495		}
 496
 497	}
 498
 499	private void checkAndSendNextMessage(SurespotMessage message) {
 500		sendMessages();
 501
 502		if (mResendBuffer.size() > 0) {
 503			if (mResendBuffer.remove(message)) {
 504				SurespotLog.v(TAG, "Received and removed message from resend  buffer: " + message);
 505			}
 506		}
 507	}
 508
 509	private SurespotMessage[] getResendMessages() {
 510		SurespotMessage[] messages = mResendBuffer.toArray(new SurespotMessage[0]);
 511		// mResendBuffer.clear();
 512		return messages;
 513
 514	}
 515
 516	private void enqueueMessage(SurespotMessage message) {
 517		mSendBuffer.add(message);
 518	}
 519
 520	private synchronized void sendMessages() {
 521		if (mBackgroundTimer == null) {
 522			mBackgroundTimer = new Timer("backgroundTimer");
 523		}
 524
 525		SurespotLog.v(TAG, "Sending: " + mSendBuffer.size() + " messages.");
 526
 527		Iterator<SurespotMessage> iterator = mSendBuffer.iterator();
 528		while (iterator.hasNext()) {
 529			SurespotMessage message = iterator.next();
 530			if (isMessageReadyToSend(message)) {
 531				iterator.remove();
 532				sendMessage(message);
 533			}
 534			else {
 535				break;
 536			}
 537		}
 538
 539	}
 540
 541	private boolean isMessageReadyToSend(SurespotMessage message) {
 542		return !TextUtils.isEmpty(message.getData()) && !TextUtils.isEmpty(message.getFromVersion()) && !TextUtils.isEmpty(message.getToVersion());
 543	}
 544
 545	private void sendMessage(final SurespotMessage message) {
 546		SurespotLog.v(TAG, "sendmessage adding message to ResendBuffer, text: %s, iv: %s", message.getPlainData(), message.getIv());
 547
 548		mResendBuffer.add(message);
 549		if (getState() == STATE_CONNECTED) {
 550			SurespotLog.v(TAG, "sendmessage, socket: %s", socket);
 551			JSONObject json = message.toJSONObject();
 552			SurespotLog.v(TAG, "sendmessage, json: %s", json);
 553			String s = json.toString();
 554			SurespotLog.v(TAG, "sendmessage, message string: %s", s);
 555
 556			if (socket != null) {
 557				socket.send(s);
 558			}
 559		}
 560	}
 561
 562	private int getState() {
 563		return mConnectionState;
 564	}
 565
 566	private synchronized void setState(int state) {
 567		mConnectionState = state;
 568	}
 569
 570	private ReconnectTask mReconnectTask;
 571
 572	private class ReconnectTask extends TimerTask {
 573
 574		@Override
 575		public void run() {
 576			SurespotLog.v(TAG, "Reconnect task run.");
 577			connect();
 578		}
 579	}
 580
 581	private void handleMessage(final SurespotMessage message) {
 582		SurespotLog.v(TAG, "handleMessage %s", message);
 583		final String otherUser = message.getOtherUser();
 584
 585		final ChatAdapter chatAdapter = mChatAdapters.get(otherUser);
 586
 587		// if the adapter is open add the message
 588		if (chatAdapter != null) {
 589
 590			// decrypt the message before adding it so the size is set properly
 591			new AsyncTask<Void, Void, Void>() {
 592
 593				@Override
 594				protected Void doInBackground(Void... params) {
 595					if (message.getMimeType().equals(SurespotConstants.MimeTypes.TEXT)) {
 596
 597						// decrypt it before adding
 598						final String plainText = EncryptionController.symmetricDecrypt(message.getOurVersion(), message.getOtherUser(),
 599								message.getTheirVersion(), message.getIv(), message.getData());
 600
 601						// substitute emoji
 602						if (plainText != null) {
 603							EmojiParser parser = EmojiParser.getInstance();
 604							message.setPlainData(parser.addEmojiSpans(plainText));
 605						}
 606					}
 607
 608					else {
 609						if (message.getMimeType().equals(SurespotConstants.MimeTypes.IMAGE)) {
 610
 611							// if it's an image that i sent
 612							// get the local message
 613							if (ChatUtils.isMyMessage(message)) {
 614								handleCachedFile(chatAdapter, message);
 615							}
 616							else {
 617
 618								InputStream imageStream = MainActivity.getNetworkController().getFileStream(MainActivity.getContext(), message.getData());
 619
 620								Bitmap bitmap = null;
 621								PipedOutputStream out = new PipedOutputStream();
 622								PipedInputStream inputStream;
 623								try {
 624									inputStream = new PipedInputStream(out);
 625
 626									EncryptionController.runDecryptTask(message.getOurVersion(), message.getOtherUser(), message.getTheirVersion(),
 627											message.getIv(), new BufferedInputStream(imageStream), out);
 628
 629									byte[] bytes = Utils.inputStreamToBytes(inputStream);
 630
 631									bitmap = ChatUtils.getSampledImage(bytes);
 632								}
 633								catch (InterruptedIOException ioe) {
 634
 635									SurespotLog.w(TAG, ioe, "handleMessage");
 636								}
 637								catch (IOException e) {
 638									SurespotLog.w(TAG, e, "handleMessage");
 639								}
 640
 641								if (bitmap != null) {
 642									MessageImageDownloader.addBitmapToCache(message.getData(), bitmap);
 643								}
 644							}
 645						}
 646						else {
 647							if (message.getMimeType().equals(SurespotConstants.MimeTypes.M4A)) {
 648								if (ChatUtils.isMyMessage(message)) {
 649									handleCachedFile(chatAdapter, message);
 650								}
 651								else {
 652
 653									InputStream encryptedVoiceStream = MainActivity.getNetworkController().getFileStream(MainActivity.getContext(),
 654											message.getData());
 655
 656									PipedOutputStream out = new PipedOutputStream();
 657									PipedInputStream inputStream;
 658									try {
 659										inputStream = new PipedInputStream(out);
 660
 661										EncryptionController.runDecryptTask(message.getOurVersion(), message.getOtherUser(), message.getTheirVersion(),
 662												message.getIv(), new BufferedInputStream(encryptedVoiceStream), out);
 663
 664										byte[] bytes = Utils.inputStreamToBytes(inputStream);
 665										message.setPlainBinaryData(bytes);
 666									}
 667									catch (InterruptedIOException ioe) {
 668
 669										SurespotLog.w(TAG, ioe, "handleMessage");
 670
 671									}
 672									catch (IOException e) {
 673										SurespotLog.w(TAG, e, "handleMessage");
 674									}
 675								}
 676							}
 677							else {
 678								message.setPlainData("unknown message mime type");
 679							}
 680						}
 681					}
 682					return null;
 683				}
 684
 685				protected void onPostExecute(Void result) {
 686					try {
 687						boolean added = applyControlMessages(chatAdapter, message, false, false, true);
 688						scrollToEnd(otherUser);
 689
 690						Friend friend = mFriendAdapter.getFriend(otherUser);
 691						if (friend != null) {
 692							int messageId = message.getId();
 693
 694							// always update the available id
 695							friend.setAvailableMessageId(messageId);
 696
 697							// if the chat is showing set the last viewed id the id of the message we just received
 698							if (otherUser.equals(mCurrentChat)) {
 699
 700								friend.setLastViewedMessageId(messageId);
 701
 702								// if it was a voice message from the other user set play flag
 703								// TODO wrap in preference
 704								if (!ChatUtils.isMyMessage(message) && message.getMimeType().equals(SurespotConstants.MimeTypes.M4A)) {
 705									message.setPlayMedia(true);
 706								}
 707
 708							}
 709							// chat not showing
 710							else {
 711								// if it's my message increment the count by one to account for it as I may have unread messages from the
 712								// other user; we
 713								// can't just set the last viewed to the latest message
 714								if (ChatUtils.isMyMessage(message) && added) {
 715									int adjustedLastViewedId = friend.getLastViewedMessageId() + 1;
 716									if (adjustedLastViewedId < messageId) {
 717										friend.setLastViewedMessageId(adjustedLastViewedId);
 718									}
 719									else {
 720										friend.setLastViewedMessageId(messageId);
 721									}
 722								}
 723							}
 724
 725							mFriendAdapter.sort();
 726							mFriendAdapter.notifyDataSetChanged();
 727						}
 728
 729					}
 730					catch (SurespotMessageSequenceException e) {
 731						SurespotLog.v(TAG, "handleMessage: %s", e.getMessage());
 732						getLatestMessagesAndControls(otherUser, e.getMessageId());
 733					}
 734				};
 735
 736			}.execute();
 737
 738		}
 739		else {
 740			Friend friend = mFriendAdapter.getFriend(otherUser);
 741			if (friend != null) {
 742				int messageId = message.getId();
 743
 744				// always update the available id
 745				friend.setAvailableMessageId(messageId);
 746
 747				mFriendAdapter.sort();
 748				mFriendAdapter.notifyDataSetChanged();
 749			}
 750		}
 751
 752	}
 753
 754	private boolean applyControlMessages(ChatAdapter chatAdapter, SurespotMessage message, boolean checkSequence, boolean sort, boolean notify) throws SurespotMessageSequenceException {
 755		// see if we have applicable control messages and apply them if necessary
 756		ArrayList<SurespotControlMessage> controlMessages = chatAdapter.getControlMessages();
 757		ArrayList<SurespotControlMessage> applicableControlMessages = new ArrayList<SurespotControlMessage>();
 758		for (SurespotControlMessage controlMessage : controlMessages) {
 759			int messageId = Integer.parseInt(controlMessage.getMoreData());
 760			if (message.getId() == messageId) {
 761				applicableControlMessages.add(controlMessage);
 762			}
 763		}
 764		boolean added = false;
 765
 766		if (applicableControlMessages.size() == 0) {
 767
 768			added = chatAdapter.addOrUpdateMessage(message, checkSequence, sort, notify);
 769
 770		}
 771		else {
 772			added = chatAdapter.addOrUpdateMessage(message, checkSequence, false, false);
 773
 774			for (SurespotControlMessage controlMessage : applicableControlMessages) {
 775				SurespotLog.v(TAG, "applying control message %s: to message %s", controlMessage, message);
 776				handleControlMessage(chatAdapter, controlMessage, false, true);
 777			}
 778
 779			if (notify) {
 780				chatAdapter.notifyDataSetChanged();
 781			}
 782		}
 783
 784		return added;
 785	}
 786
 787	// add entry to http cache for image we sent so we don't download it again
 788	private void handleCachedFile(ChatAdapter chatAdapter, SurespotMessage message) {
 789		SurespotLog.v(TAG, "handleCachedFile");
 790		SurespotMessage localMessage = chatAdapter.getMessageByIv(message.getIv());
 791
 792		// if the data is different we haven't updated the url to point externally
 793		if (localMessage != null && localMessage.getId() == null && !localMessage.getData().equals(message.getData())) {
 794			// add the remote cache entry for the new url
 795
 796			String localUri = localMessage.getData();
 797			String remoteUri = message.getData();
 798
 799			FileInputStream fis;
 800			try {
 801				fis = new FileInputStream(new File(new URI(localUri)));
 802				byte[] imageData = Utils.inputStreamToBytes(fis);
 803
 804				HeapResource resource = new HeapResource(imageData);
 805				Date date = new Date();
 806				String sDate = DateUtils.formatDate(date);
 807
 808				Header[] cacheHeaders = new Header[3];
 809
 810				// create fake cache entry
 811				cacheHeaders[0] = new BasicHeader("Last-Modified", sDate);
 812				cacheHeaders[1] = new BasicHeader("Cache-Control", "public, max-age=31557600");
 813				cacheHeaders[2] = new BasicHeader("Date", sDate);
 814
 815				HttpCacheEntry cacheEntry = new HttpCacheEntry(date, date, mImageStatusLine, cacheHeaders, resource);
 816
 817				SurespotLog.v(TAG, "creating http cache entry for: %s", remoteUri);
 818				mNetworkController.addCacheEntry(remoteUri, cacheEntry);
 819
 820				// update image cache
 821				if (message.getMimeType().equals(SurespotConstants.MimeTypes.IMAGE)) {
 822					MessageImageDownloader.copyAndRemoveCacheEntry(localUri, remoteUri);
 823				}
 824
 825			}
 826			catch (FileNotFoundException e1) {
 827				SurespotLog.w(TAG, e1, "onMessage");
 828			}
 829			catch (URISyntaxException e1) {
 830				SurespotLog.w(TAG, e1, "onMessage");
 831			}
 832			catch (IOException e) {
 833				SurespotLog.w(TAG, e, "onMessage");
 834			}
 835
 836			// delete the file
 837
 838			try {
 839				SurespotLog.v(TAG, "handleCachedImage deleting local file: %s", localUri);
 840
 841				File file = new File(new URI(localUri));
 842				file.delete();
 843			}
 844			catch (URISyntaxException e) {
 845				SurespotLog.w(TAG, e, "handleMessage");
 846			}
 847
 848			// update message to point to real location
 849			localMessage.setData(remoteUri);
 850
 851		}
 852	}
 853
 854	// private void handleLocalData(ChatAdapter chatAdapter, SurespotMessage message) {
 855	// SurespotLog.v(TAG, "handleLocalData");
 856	// SurespotMessage localMessage = chatAdapter.getMessageByIv(message.getIv());
 857	//
 858	// // if the data is different we haven't updated the http cache with data we sent
 859	// if (localMessage != null && localMessage.getId() == null && !localMessage.getData().equals(message.getData()) && localMessage.getInlineData() != null) {
 860	// // add the remote cache entry for the new url
 861	//
 862	// byte[] imageData = localMessage.getInlineData();
 863	//
 864	// String remoteUri = message.getData();
 865	// HeapResource resource = new HeapResource(imageData);
 866	// Date date = new Date();
 867	// String sDate = DateUtils.formatDate(date);
 868	//
 869	// Header[] cacheHeaders = new Header[3];
 870	//
 871	// // create fake cache entry
 872	// cacheHeaders[0] = new BasicHeader("Last-Modified", sDate);
 873	// cacheHeaders[1] = new BasicHeader("Cache-Control", "public, max-age=31557600");
 874	// cacheHeaders[2] = new BasicHeader("Date", sDate);
 875	//
 876	// HttpCacheEntry cacheEntry = new HttpCacheEntry(date, date, mImageStatusLine, cacheHeaders, resource);
 877	//
 878	// SurespotLog.v(TAG, "creating http cache entry for: %s", remoteUri);
 879	// mNetworkController.addCacheEntry(remoteUri, cacheEntry);
 880	//
 881	// // update message to point to real location
 882	// localMessage.setData(remoteUri);
 883	//
 884	// // clear out the inline data as we should still have the decrypted plain data
 885	// localMessage.setInlineData(null);
 886	//
 887	// }
 888	// }
 889
 890	// message handling shiznit
 891
 892	void loadEarlierMessages(final String username, final IAsyncCallback<Boolean> callback) {
 893
 894		// mLoading = true;
 895		// get the list of messages
 896
 897		Integer firstMessageId = mEarliestMessage.get(username);
 898		if (firstMessageId == null) {
 899			firstMessageId = getEarliestMessageId(username);
 900			mEarliestMessage.put(username, firstMessageId);
 901		}
 902		// else {
 903		// firstMessageId -= 60;
 904		// if (firstMessageId < 1) {
 905		// firstMessageId = 1;
 906		// }
 907		// }
 908
 909		if (firstMessageId != null) {
 910
 911			if (firstMessageId > 1) {
 912
 913				SurespotLog.v(TAG, username + ": asking server for messages before messageId: " + firstMessageId);
 914				// final int fMessageId = firstMessageId;
 915				final ChatAdapter chatAdapter = mChatAdapters.get(username);
 916
 917				mNetworkController.getEarlierMessages(username, firstMessageId, new JsonHttpResponseHandler() {
 918					@Override
 919					public void onSuccess(final JSONArray jsonArray) {
 920
 921						// if (getActivity() != null) {
 922						SurespotMessage message = null;
 923
 924						try {
 925							for (int i = jsonArray.length() - 1; i >= 0; i--) {
 926								JSONObject jsonMessage = new JSONObject(jsonArray.getString(i));
 927								message = SurespotMessage.toSurespotMessage(jsonMessage);
 928								chatAdapter.insertMessage(message, false);
 929							}
 930						}
 931						catch (JSONException e) {
 932							SurespotLog.e(TAG, e, "%s: error creating chat message", username);
 933						}
 934
 935						SurespotLog.v(TAG, "%s: loaded: %d earlier messages from the server.", username, jsonArray.length());
 936						if (message != null) {
 937							mEarliestMessage.put(username, message.getId());
 938							// chatAdapter.notifyDataSetChanged();
 939						}
 940
 941						// chatAdapter.setLoading(false);
 942						callback.handleResponse(jsonArray.length() > 0);
 943
 944					}
 945
 946					@Override
 947					public void onFailure(Throwable error, String content) {
 948						SurespotLog.i(TAG, error, "%s: getEarlierMessages", username);
 949						// chatAdapter.setLoading(false);
 950						callback.handleResponse(false);
 951					}
 952				});
 953			}
 954			else {
 955				SurespotLog.v(TAG, "%s: getEarlierMessages: no more messages.", username);
 956				callback.handleResponse(false);
 957				// ChatFragment.this.mNoEarlierMessages = true;
 958			}
 959
 960		}
 961	}
 962
 963	private void getLatestIds() {
 964		SurespotLog.v(TAG, "getLatestIds");
 965		// setMessagesLoading(true);
 966
 967		mNetworkController.getLatestIds(mLatestUserControlId, new JsonHttpResponseHandler() {
 968
 969			@Override
 970			public void onSuccess(int statusCode, final JSONObject jsonResponse) {
 971				SurespotLog.v(TAG, "getlatestIds success, response: %s, statusCode: %d", jsonResponse, statusCode);
 972				JSONArray conversationIds = jsonResponse.optJSONArray("conversationIds");
 973
 974				Friend friend = null;
 975				if (conversationIds != null) {
 976					for (int i = 0; i < conversationIds.length(); i++) {
 977						try {
 978							JSONObject jsonObject = conversationIds.getJSONObject(i);
 979							String spot = jsonObject.getString("conversation");
 980							Integer availableId = jsonObject.getInt("id");
 981							String user = ChatUtils.getOtherSpotUser(spot, IdentityController.getLoggedInUser());
 982							// update available ids
 983							friend = mFriendAdapter.getFriend(user);
 984							if (friend != null) {
 985								friend.setAvailableMessageId(availableId);
 986							}
 987
 988						}
 989						catch (JSONException e) {
 990							SurespotLog.w(TAG, "getlatestIds", e);
 991						}
 992					}
 993				}
 994
 995				JSONArray controlIds = jsonResponse.optJSONArray("controlIds");
 996				if (controlIds != null) {
 997					for (int i = 0; i < controlIds.length(); i++) {
 998						try {
 999							JSONObject jsonObject = controlIds.getJSONObject(i);
1000							String spot = jsonObject.getString("conversation");
1001							Integer availableId = jsonObject.getInt("id");
1002							String user = ChatUtils.getOtherSpotUser(spot, IdentityController.getLoggedInUser());
1003							// update available ids
1004							friend = mFriendAdapter.getFriend(user);
1005							if (friend != null) {
1006								friend.setAvailableMessageControlId(availableId);
1007							}
1008						}
1009						catch (JSONException e) {
1010							SurespotLog.w(TAG, "getlatestIds", e);
1011						}
1012					}
1013				}
1014
1015				JSONArray userControlMessages = jsonResponse.optJSONArray("userControlMessages");
1016				if (userControlMessages != null) {
1017					handleControlMessages(IdentityController.getLoggedInUser(), userControlMessages);
1018				}
1019
1020				if (friend != null) {
1021					mFriendAdapter.sort();
1022					mFriendAdapter.notifyDataSetChanged();
1023				}
1024
1025				getLatestMessagesAndControls();
1026
1027			}
1028
1029			@Override
1030			public void onFailure(Throwable error, String content) {
1031				// setMessagesLoading(false);
1032				SurespotLog.i(TAG, error, "loading latest messages failed");
1033				Utils.makeToast(mContext, mContext.getString(R.string.loading_latest_messages_failed));
1034				setProgress(null, false);
1035			}
1036		});
1037
1038	}
1039
1040	private class LatestIdPair {
1041		public int latestMessageId;
1042		public int latestControlMessageId;
1043	}
1044
1045	private void getLatestMessagesAndControls() {
1046		for (Entry<String, ChatAdapter> entry : mChatAdapters.entrySet()) {
1047			getLatestMessagesAndControls(entry.getKey());
1048		}
1049
1050		// done with "global" updates
1051		setProgress(null, false);
1052	}
1053
1054	private LatestIdPair getLatestIds(String username) {
1055		Friend friend = getFriendAdapter().getFriend(username);
1056		LatestIdPair idPair = mPreConnectIds.get(username);
1057
1058		Integer latestMessageId = idPair.latestMessageId > -1 ? idPair.latestMessageId : 0;
1059		int latestAvailableId = friend.getAvailableMessageId();
1060
1061		int latestControlId = idPair.latestControlMessageId > -1 ? idPair.latestControlMessageId : friend.getLastReceivedMessageControlId();
1062		int latestAvailableControlId = friend.getAvailableMessageControlId();
1063
1064		int fetchMessageId = 0;
1065		if (latestMessageId > 0) {
1066			fetchMessageId = latestAvailableId > latestMessageId ? latestMessageId : -1;
1067		}
1068
1069		int fetchControlMessageId = 0;
1070		if (latestControlId > 0) {
1071			fetchControlMessageId = latestAvailableControlId > latestControlId ? latestControlId : -1;
1072		}
1073
1074		LatestIdPair intPair = new LatestIdPair();
1075		intPair.latestMessageId = fetchMessageId;
1076		intPair.latestControlMessageId = fetchControlMessageId;
1077
1078		return intPair;
1079	}
1080
1081	private void getLatestMessagesAndControls(final String username) {
1082
1083		LatestIdPair ids = getLatestIds(username);
1084
1085		getLatestMessagesAndControls(username, ids.latestMessageId, ids.latestControlMessageId);
1086
1087	}
1088
1089	private void getLatestMessagesAndControls(String username, int messageId) {
1090		getLatestMessagesAndControls(username, messageId, -1);
1091
1092	}
1093
1094	private void getLatestMessagesAndControls(final String username, int fetchMessageId, int fetchControlMessageId) {
1095		SurespotLog.v(TAG, "getLatestMessagesAndControls: name %s, fetchMessageId: %d, fetchControlMessageId: %d", username, fetchMessageId,
1096				fetchControlMessageId);
1097		if (fetchMessageId > -1 || fetchControlMessageId > -1) {
1098			setProgress(username, true);
1099
1100			mNetworkController.getMessageData(username, fetchMessageId, fetchControlMessageId, new JsonHttpResponseHandler() {
1101				@Override
1102				public void onSuccess(int statusCode, JSONObject response) {
1103
1104					JSONArray controlMessages = response.optJSONArray("controlMessages");
1105
1106					String messages = response.optString("messages", null);
1107					if (messages != null) {
1108						handleMessages(username, messages);
1109					}
1110
1111					if (controlMessages != null) {
1112						handleControlMessages(username, controlMessages);
1113					}
1114
1115					setProgress(username, false);
1116
1117				}
1118			});
1119		}
1120
1121	}
1122
1123	private void handleControlMessages(String username, JSONArray jsonArray) {
1124		SurespotLog.v(TAG, "%s: handleControlMessages", username);
1125		final ChatAdapter chatAdapter = mChatAdapters.get(username);
1126
1127		SurespotControlMessage message = null;
1128		boolean messageActivity = false;
1129		boolean userActivity = false;
1130		for (int i = 0; i < jsonArray.length(); i++) {
1131			try {
1132				JSONObject jsonMessage = new JSONObject(jsonArray.getString(i));
1133				message = SurespotControlMessage.toSurespotControlMessage(jsonMessage);
1134				handleControlMessage(chatAdapter, message, false, false);
1135				// if it's a system message from another user then check version
1136				if (message.getType().equals("user")) {
1137					userActivity = true;
1138				}
1139				else
1140					if (message.getType().equals("message")) {
1141						messageActivity = true;
1142					}
1143
1144			}
1145			catch (JSONException e) {
1146				SurespotLog.w(TAG, e, "%s: error creating chat message", username);
1147			}
1148
1149		}
1150
1151		if (message != null) {
1152
1153			SurespotLog.v(TAG, "%s: loaded: %d latest control messages from the server.", username, jsonArray.length());
1154
1155			if (messageActivity || userActivity) {
1156				Friend friend = mFriendAdapter.getFriend(username);
1157				if (friend != null) {
1158
1159					if (messageActivity) {
1160
1161						if (chatAdapter != null) {
1162							friend.setLastReceivedMessageControlId(message.getId());
1163							chatAdapter.sort();
1164							chatAdapter.notifyDataSetChanged();
1165						}
1166
1167						friend.setAvailableMessageControlId(message.getId());
1168						mFriendAdapter.notifyDataSetChanged();
1169
1170					}
1171
1172					if (userActivity) {
1173						friend.setLastReceivedUserControlId(message.getId());
1174						saveFriends();
1175						mFriendAdapter.notifyDataSetChanged();
1176					}
1177				}
1178			}
1179		}
1180
1181		// chatAdapter.setLoading(false);
1182	}
1183
1184	private void handleControlMessage(ChatAdapter chatAdapter, SurespotControlMessage message, boolean notify, boolean reApplying) {
1185		// if it's a system message from another user then check version
1186		if (message.getType().equals("user")) {
1187			handleUserControlMessage(message, notify);
1188		}
1189		else
1190			if (message.getType().equals("message")) {
1191				String otherUser = ChatUtils.getOtherSpotUser(message.getData(), IdentityController.getLoggedInUser());
1192				Friend friend = mFriendAdapter.getFriend(otherUser);
1193
1194				if (chatAdapter == null) {
1195					chatAdapter = mChatAdapters.get(otherUser);
1196				}
1197
1198				if (chatAdapter != null) {
1199					// if we're not re applying this control message
1200					if (!reApplying) {
1201						// add control message to check messages against later for this session
1202						chatAdapter.addControlMessage(message);
1203					}
1204
1205					boolean controlFromMe = message.getFrom().equals(IdentityController.getLoggedInUser());
1206					if (message.getAction().equals("delete")) {
1207						int messageId = Integer.parseInt(message.getMoreData());
1208						SurespotMessage dMessage = chatAdapter.getMessageById(messageId);
1209
1210						if (dMessage != null) {
1211							deleteMessageInternal(chatAdapter, dMessage, controlFromMe);
1212						}
1213					}
1214					else {
1215						if (message.getAction().equals("deleteAll")) {
1216							if (message.getMoreData() != null) {
1217								if (controlFromMe) {
1218									chatAdapter.deleteAllMessages(Integer.parseInt(message.getMoreData()));
1219								}
1220								else {
1221									chatAdapter.deleteTheirMessages(Integer.parseInt(message.getMoreData()));
1222								}
1223							}
1224						}
1225						else {
1226							if (message.getAction().equals("shareable") || message.getAction().equals("notshareable")) {
1227								int messageId = Integer.parseInt(message.getMoreData());
1228								SurespotMessage dMessage = chatAdapter.getMessageById(messageId);
1229								if (dMessage != null) {
1230									SurespotLog.v(TAG, "setting message " + message.getAction());
1231									dMessage.setShareable(message.getAction().equals("shareable") ? true : false);
1232								}
1233							}
1234						}
1235					}
1236				}
1237
1238				if (notify) {
1239					if (friend != null) {
1240						// if the chat adapter is open we will have acted upon the control message
1241						if (chatAdapter != null) {
1242							friend.setLastReceivedMessageControlId(message.getId());
1243						}
1244
1245						friend.setAvailableMessageControlId(message.getId());
1246					}
1247
1248					if (chatAdapter != null) {
1249						chatAdapter.notifyDataSetChanged();
1250					}
1251				}
1252			}
1253	}
1254
1255	private void handleUserControlMessage(SurespotControlMessage message, boolean notify) {
1256
1257		mLatestUserControlId = message.getId();
1258		String user = null;
1259
1260		if (message.getAction().equals("revoke")) {
1261			IdentityController.updateLatestVersion(mContext, message.getData(), message.getMoreData());
1262		}
1263		else
1264			if (message.getAction().equals("invited")) {
1265				user = message.getData();
1266				mFriendAdapter.addFriendInvited(user);
1267			}
1268			else
1269				if (message.getAction().equals("added")) {
1270					user = message.getData();
1271					mFriendAdapter.addNewFriend(user);
1272					ChatAdapter chatAdapter = mChatAdapters.get(user);
1273
1274					if (chatAdapter != null) {
1275						chatAdapter.userDeleted(false);
1276					}
1277				}
1278				else
1279					if (message.getAction().equals("invite")) {
1280						user = message.getData();
1281						mFriendAdapter.addFriendInviter(user);
1282					}
1283					else
1284						if (message.getAction().equals("ignore")) {
1285							String friendName = message.getData();
1286							Friend friend = mFriendAdapter.getFriend(friendName);
1287
1288							// if they're not deleted, remove them
1289							if (friend != null) {
1290								if (!friend.isDeleted()) {
1291
1292									mFriendAdapter.removeFriend(friendName);
1293								}
1294								else {
1295									// they've been deleted, just remove the invite flags
1296									friend.setInviter(false);
1297									friend.setInvited(false);
1298
1299								}
1300							}
1301
1302						}
1303						else
1304							if (message.getAction().equals("delete")) {
1305								String friendName = message.getData();
1306
1307								Friend friend = mFriendAdapter.getFriend(friendName);
1308
1309								if (friend != null) {
1310									// if it was just a delete of an invite
1311									if (friend.isInviter() || friend.isInvited()) {
1312
1313										// if they're not deleted, remove them
1314										if (!friend.isDeleted()) {
1315											mFriendAdapter.removeFriend(friendName);
1316										}
1317										else {
1318											// they've been deleted, just remove the invite flags
1319											friend.setInviter(false);
1320											friend.setInvited(false);
1321										}
1322
1323										// clear any associated invite notification
1324										String loggedInUser = IdentityController.getLoggedInUser();
1325										if (loggedInUser != null) {
1326											mNotificationManager.cancel(loggedInUser + ":" + friendName,
1327													SurespotConstants.IntentRequestCodes.INVITE_REQUEST_NOTIFICATION);
1328										}
1329
1330									}
1331									// they really deleted us boo hoo
1332									else {
1333										handleDeleteUser(friendName, message.getMoreData(), notify);
1334									}
1335
1336								}
1337
1338							}
1339
1340		if (notify) {
1341
1342			Friend friend = mFriendAdapter.getFriend(user);
1343			if (friend != null) {
1344				friend.setLastReceivedUserControlId(message.getId());
1345			}
1346			mFriendAdapter.notifyDataSetChanged();
1347			saveFriends();
1348		}
1349
1350	}
1351
1352	private void handleDeleteUser(String deletedUser, String deleter, boolean notify) {
1353		SurespotLog.v(TAG, "handleDeleteUser,  deletedUser: %s, deleter: %s", deletedUser, deleter);
1354		String username = IdentityController.getLoggedInUser();
1355
1356		Friend friend = mFriendAdapter.getFriend(deletedUser);
1357
1358		boolean iDidTheDeleting = deleter.equals(username);
1359		if (iDidTheDeleting) {
1360			// won't be needing this anymore
1361			closeTab(deletedUser);
1362
1363			// blow all the state associated with this user away
1364			StateController.wipeUserState(mContext, username, deletedUser);
1365
1366			// clear in memory cached data
1367			SurespotApplication.getCachingService().clearUserData(deletedUser);
1368
1369			// clear the http cache
1370			mNetworkController.clearCache();
1371			// or you
1372			mFriendAdapter.removeFriend(deletedUser);
1373		}
1374		// you deleted me, you bastard!!
1375		else {
1376			ChatAdapter chatAdapter = mChatAdapters.get(deleter);
1377
1378			// i'll delete all your messages then
1379			if (chatAdapter != null) {
1380				chatAdapter.userDeleted(true);
1381				if (notify) {
1382					chatAdapter.notifyDataSetChanged();
1383				}
1384			}
1385
1386			// and mark you as deleted until I want to delete you
1387			friend.setDeleted();
1388
1389			// force the controls to update
1390			if (friend != null && mCurrentChat != null && mCurrentChat.equals(deletedUser)) {
1391				mTabShowingCallback.handleResponse(friend);
1392			}
1393		}
1394
1395		enableMenuItems(friend);
1396	}
1397
1398	private void handleErrorMessage(SurespotErrorMessage errorMessage) {
1399		SurespotMessage message = null;
1400		Iterator<SurespotMessage> iterator = mResendBuffer.iterator();
1401		while (iterator.hasNext()) {
1402			message = iterator.next();
1403			if (message.getIv().equals(errorMessage.getId())) {
1404				iterator.remove();
1405
1406				message.setErrorStatus(errorMessage.getStatus());
1407				break;
1408			}
1409		}
1410
1411		if (message != null) {
1412			ChatAdapter chatAdapter = mChatAdapters.get(message.getOtherUser());
1413			if (chatAdapter != null) {
1414				chatAdapter.notifyDataSetChanged();
1415			}
1416		}
1417
1418	}
1419
1420	private void deleteMessageInternal(ChatAdapter chatAdapter, SurespotMessage dMessage, boolean initiatedByMe) {
1421		// if it's an image blow the http cache entry away
1422		if (dMessage.getMimeType() != null) {
1423			if (dMessage.getMimeType().equals(SurespotConstants.MimeTypes.IMAGE) || dMessage.getMimeType().equals(SurespotConstants.MimeTypes.M4A)) {
1424				mNetworkController.purgeCacheUrl(dMessage.getData());
1425			}
1426
1427			boolean myMessage = dMessage.getFrom().equals(IdentityController.getLoggedInUser());
1428
1429			// if i sent the delete, or it's not my message then delete it
1430			// (if someone else deleted my message we don't care)
1431			if (initiatedByMe || !myMessage) {
1432				SurespotLog.v(TAG, "deleting message");
1433				chatAdapter.deleteMessageById(dMessage.getId());
1434			}
1435		}
1436	}
1437
1438	private void handleMessages(String username, String jsonMessageString) {
1439		SurespotLog.v(TAG, "%s: handleMessages", username);
1440		final ChatAdapter chatAdapter = mChatAdapters.get(username);
1441		if (chatAdapter == null) {
1442			return;
1443		}
1444		int sentByMeCount = 0;
1445
1446		SurespotMessage lastMessage = null;
1447		try {
1448			JSONArray jsonUM = new JSONArray(jsonMessageString);
1449			SurespotLog.v(TAG, "%s: loaded: %d messages from the server: %s", username, jsonUM.length(), jsonMessageString);
1450			for (int i = 0; i < jsonUM.length(); i++) {
1451
1452				lastMessage = SurespotMessage.toSurespotMessage(new JSONObject(jsonUM.getString(i)));
1453				boolean myMessage = lastMessage.getFrom().equals(IdentityController.getLoggedInUser());
1454
1455				if (myMessage) {
1456					if (lastMessage.getMimeType().equals(SurespotConstants.MimeTypes.IMAGE)) {
1457						handleCachedFile(chatAdapter, lastMessage);
1458					}
1459					else {
1460						if (lastMessage.getMimeType().equals(SurespotConstants.MimeTypes.M4A)) {
1461							handleCachedFile(chatAdapter, lastMessage);
1462						}
1463					}
1464				}
1465
1466				boolean added = applyControlMessages(chatAdapter, lastMessage, false, false, false);
1467
1468				mResendBuffer.remove(lastMessage);
1469				if (added && myMessage) {
1470					sentByMeCount++;
1471				}
1472			}
1473		}
1474		catch (JSONException e) {
1475			SurespotLog.w(TAG, e, "jsonStringsToMessages");
1476
1477		}
1478		catch (SurespotMessageSequenceException e) {
1479			// shouldn't happen
1480			SurespotLog.w(TAG, e, "handleMessages");
1481			// getLatestMessagesAndControls(username, e.getMessageId(), -1);
1482			// setProgress(username, false);
1483			return;
1484		}
1485
1486		if (lastMessage != null) {
1487			Friend friend = mFriendAdapter.getFriend(username);
1488
1489			int availableId = lastMessage.getId();
1490			friend.setAvailableMessageId(availableId);
1491
1492			int lastViewedId = friend.getLastViewedMessageId();
1493
1494			// how many new messages total are there
1495			int delta = availableId - lastViewedId;
1496
1497			// if the current chat is showing or
1498			// all the new messages are mine then i've viewed them all
1499			if (username.equals(mCurrentChat) || sentByMeCount == delta) {
1500				friend.setLastViewedMessageId(availableId);
1501			}
1502			else {
1503				// set the last viewed id to the difference caused by their messages
1504				friend.setLastViewedMessageId(availableId - (delta - sentByMeCount));
1505			}
1506
1507			chatAdapter.sort();
1508			chatAdapter.notifyDataSetChanged();
1509			chatAdapter.doneCheckingSequence();
1510			mFriendAdapter.sort();
1511			mFriendAdapter.notifyDataSetChanged();
1512
1513			scrollToEnd(username);
1514		}
1515
1516		// setProgress(username, false);
1517	}
1518
1519	private Integer getEarliestMessageId(String username) {
1520
1521		ChatAdapter chatAdapter = mChatAdapters.get(username);
1522		Integer firstMessageId = null;
1523		if (chatAdapter != null) {
1524			SurespotMessage firstMessage = chatAdapter.getFirstMessageWithId();
1525
1526			if (firstMessage != null) {
1527				firstMessageId = firstMessage.getId();
1528			}
1529
1530		}
1531		return firstMessageId;
1532	}
1533
1534	private int getLatestMessageId(String username) {
1535		Integer lastMessageId = 0;
1536		ChatAdapter chatAdapter = mChatAdapters.get(username);
1537		if (chatAdapter != null) {
1538
1539			SurespotMessage lastMessage = chatAdapter.getLastMessageWithId();
1540			if (lastMessage != null) {
1541				lastMessageId = lastMessage.getId();
1542			}
1543		}
1544		return lastMessageId;
1545
1546	}
1547
1548	private Integer getLatestMessageControlId(String username) {
1549		Friend friend = mFriendAdapter.getFriend(username);
1550		Integer lastControlId = null;
1551		if (friend != null) {
1552			lastControlId = friend.getLastReceivedMessageControlId();
1553		}
1554		return lastControlId == null ? 0 : lastControlId;
1555	}
1556
1557	public synchronized void loadMessages(String username, boolean replace) {
1558		SurespotLog.v(TAG, "loadMessages: " + username);
1559
1560		String loggedInUser = IdentityController.getLoggedInUser();
1561
1562		if (!TextUtils.isEmpty(loggedInUser)) {
1563			String spot = ChatUtils.getSpot(loggedInUser, username);
1564			ChatAdapter chatAdapter = mChatAdapters.get(username);
1565			if (replace) {
1566				chatAdapter.setMessages(SurespotApplication.getStateController().loadMessages(spot));
1567			}
1568			else {
1569				chatAdapter.addOrUpdateMessages(S

Large files files are truncated, but you can click here to view the full file