PageRenderTime 95ms CodeModel.GetById 3ms app.highlight 77ms RepoModel.GetById 1ms app.codeStats 1ms

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

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

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