basic muc support. reworked contact list stuff

Daniel Gultsch created

Change summary

AndroidManifest.xml                                     |   7 
src/de/gultsch/chat/entities/Contact.java               |  24 +
src/de/gultsch/chat/entities/Conversation.java          |  20 
src/de/gultsch/chat/persistance/DatabaseBackend.java    |  61 ++
src/de/gultsch/chat/services/XmppConnectionService.java | 264 ++++++++--
src/de/gultsch/chat/ui/ConversationActivity.java        |  16 
src/de/gultsch/chat/ui/ConversationFragment.java        |  34 
src/de/gultsch/chat/ui/ManageAccountActivity.java       |   4 
src/de/gultsch/chat/ui/NewConversationActivity.java     | 130 +---
src/de/gultsch/chat/utils/UIHelper.java                 |   4 
src/de/gultsch/chat/xmpp/MessagePacket.java             |   7 
src/de/gultsch/chat/xmpp/XmppConnection.java            |   1 
12 files changed, 396 insertions(+), 176 deletions(-)

Detailed changes

AndroidManifest.xml 🔗

@@ -25,9 +25,14 @@
             android:windowSoftInputMode="stateHidden">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+             <intent-filter>
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="imto" />
+                <data android:host="jabber" />
+            </intent-filter>
         </activity>
         <activity
             android:name="de.gultsch.chat.ui.SettingsActivity"

src/de/gultsch/chat/entities/Contact.java 🔗

@@ -28,6 +28,9 @@ public class Contact extends AbstractEntity implements Serializable {
 	protected String photoUri;
 	protected String openPGPKey;
 	protected long lastOnlinePresence;
+
+
+	protected Account account;
 	
 	public Contact(Account account, String displayName, String jid, String photoUri) {
 		if (account == null) {
@@ -95,4 +98,25 @@ public class Contact extends AbstractEntity implements Serializable {
 				cursor.getLong(cursor.getColumnIndex(LASTONLINEPRESENCE))
 				);
 	}
+
+	public void setSubscription(String subscription) {
+		this.subscription = subscription;
+	}
+
+	public void setSystemAccount(int account) {
+		this.systemAccount = account;
+	}
+
+	public void setAccount(Account account) {
+		this.account = account;
+		this.accountUuid = account.getUuid();
+	}
+
+	public Account getAccount() {
+		return this.account;
+	}
+
+	public void setUuid(String uuid) {
+		this.uuid = uuid;
+	}
 }

src/de/gultsch/chat/entities/Conversation.java 🔗

@@ -16,6 +16,9 @@ public class Conversation extends AbstractEntity {
 	public static final int STATUS_AVAILABLE = 0;
 	public static final int STATUS_ARCHIVED = 1;
 	public static final int STATUS_DELETED = 2;
+	
+	public static final int MODE_MULTI = 1;
+	public static final int MODE_SINGLE = 0;
 
 	public static final String NAME = "name";
 	public static final String PHOTO_URI = "profilePhotoUri";
@@ -23,6 +26,7 @@ public class Conversation extends AbstractEntity {
 	public static final String CONTACT = "contactJid";
 	public static final String STATUS = "status";
 	public static final String CREATED = "created";
+	public static final String MODE = "mode";
 
 	private String name;
 	private String profilePhotoUri;
@@ -30,19 +34,20 @@ public class Conversation extends AbstractEntity {
 	private String contactJid;
 	private int status;
 	private long created;
+	private int mode;
 
 	private transient List<Message> messages = null;
 	private transient Account account = null;
 
 	public Conversation(String name, String profilePhoto, Account account,
-			String contactJid) {
+			String contactJid, int mode) {
 		this(java.util.UUID.randomUUID().toString(), name, profilePhoto, account.getUuid(), contactJid, System
-				.currentTimeMillis(), STATUS_AVAILABLE);
+				.currentTimeMillis(), STATUS_AVAILABLE,mode);
 		this.account = account;
 	}
 
 	public Conversation(String uuid, String name, String profilePhoto,
-			String accountUuid, String contactJid, long created, int status) {
+			String accountUuid, String contactJid, long created, int status, int mode) {
 		this.uuid = uuid;
 		this.name = name;
 		this.profilePhotoUri = profilePhoto;
@@ -50,6 +55,7 @@ public class Conversation extends AbstractEntity {
 		this.contactJid = contactJid;
 		this.created = created;
 		this.status = status;
+		this.mode = mode;
 	}
 
 	public List<Message> getMessages() {
@@ -132,6 +138,7 @@ public class Conversation extends AbstractEntity {
 		values.put(CONTACT, contactJid);
 		values.put(CREATED, created);
 		values.put(STATUS, status);
+		values.put(MODE,mode);
 		return values;
 	}
 
@@ -142,10 +149,15 @@ public class Conversation extends AbstractEntity {
 				cursor.getString(cursor.getColumnIndex(ACCOUNT)),
 				cursor.getString(cursor.getColumnIndex(CONTACT)),
 				cursor.getLong(cursor.getColumnIndex(CREATED)),
-				cursor.getInt(cursor.getColumnIndex(STATUS)));
+				cursor.getInt(cursor.getColumnIndex(STATUS)),
+				cursor.getInt(cursor.getColumnIndex(MODE)));
 	}
 
 	public void setStatus(int status) {
 		this.status = status;
 	}
+
+	public int getMode() {
+		return this.mode;
+	}
 }

src/de/gultsch/chat/persistance/DatabaseBackend.java 🔗

@@ -2,6 +2,7 @@ package de.gultsch.chat.persistance;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 import de.gultsch.chat.entities.Account;
 import de.gultsch.chat.entities.Contact;
@@ -37,9 +38,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				+ " TEXT, " + Conversation.PHOTO_URI + " TEXT, "
 				+ Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACT
 				+ " TEXT, " + Conversation.CREATED + " NUMBER, "
-				+ Conversation.STATUS + " NUMBER," + "FOREIGN KEY("
-				+ Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
-				+ "(" + Account.UUID + ") ON DELETE CASCADE);");
+				+ Conversation.STATUS + " NUMBER," + Conversation.MODE
+				+ " NUMBER," + "FOREIGN KEY(" + Conversation.ACCOUNT
+				+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID
+				+ ") ON DELETE CASCADE);");
 		db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
 				+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
 				+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
@@ -86,6 +88,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		SQLiteDatabase db = this.getWritableDatabase();
 		db.insert(Account.TABLENAME, null, account.getContentValues());
 	}
+	
+	public void createContact(Contact contact) {
+		SQLiteDatabase db = this.getWritableDatabase();
+		db.insert(Contact.TABLENAME, null, contact.getContentValues());
+	}
 
 	public int getConversationCount() {
 		SQLiteDatabase db = this.getReadableDatabase();
@@ -184,4 +191,52 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
 				+ "=?", args);
 	}
+	
+	public void updateContact(Contact contact) {
+		SQLiteDatabase db = this.getWritableDatabase();
+		String[] args = { contact.getUuid() };
+		db.update(Contact.TABLENAME, contact.getContentValues(), Contact.UUID
+				+ "=?", args);
+	}
+	
+	public void mergeContacts(List<Contact> contacts) {
+		SQLiteDatabase db = this.getWritableDatabase();
+		for (int i = 0; i < contacts.size(); i++) {
+			Contact contact = contacts.get(i);
+			String[] columns = {Contact.UUID};
+			String[] args = {contact.getAccount().getUuid(), contact.getJid()};
+			Cursor cursor = db.query(Contact.TABLENAME, columns,Contact.ACCOUNT+"=? AND "+Contact.JID+"=?", args, null, null, null);
+			if (cursor.getCount()>=1) {
+				cursor.moveToFirst();
+				contact.setUuid(cursor.getString(0));
+				updateContact(contact);
+			} else {
+				contact.setUuid(UUID.randomUUID().toString());
+				createContact(contact);
+			}
+		}
+	}
+
+	public List<Contact> getContacts() {
+		List<Contact> list = new ArrayList<Contact>();
+		SQLiteDatabase db = this.getReadableDatabase();
+		Cursor cursor = db.query(Contact.TABLENAME, null, null, null, null,
+				null, null);
+		while (cursor.moveToNext()) {
+			list.add(Contact.fromCursor(cursor));
+		}
+		return list;
+	}
+
+	public Contact findContact(Account account, String jid) {
+		SQLiteDatabase db = this.getReadableDatabase();
+		String[] selectionArgs = { account.getUuid(), jid };
+		Cursor cursor = db.query(Contact.TABLENAME, null,
+				Contact.ACCOUNT + "=? AND " + Contact.JID + "=?",
+				selectionArgs, null, null, null);
+		if (cursor.getCount() == 0)
+			return null;
+		cursor.moveToFirst();
+		return Contact.fromCursor(cursor);
+	}
 }

src/de/gultsch/chat/services/XmppConnectionService.java 🔗

@@ -19,14 +19,21 @@ import de.gultsch.chat.xmpp.MessagePacket;
 import de.gultsch.chat.xmpp.OnIqPacketReceived;
 import de.gultsch.chat.xmpp.OnMessagePacketReceived;
 import de.gultsch.chat.xmpp.OnStatusChanged;
+import de.gultsch.chat.xmpp.PresencePacket;
 import de.gultsch.chat.xmpp.XmppConnection;
 import android.app.NotificationManager;
 import android.app.Service;
 import android.content.Context;
+import android.content.CursorLoader;
 import android.content.Intent;
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.database.Cursor;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
+import android.provider.ContactsContract;
 import android.util.Log;
 
 public class XmppConnectionService extends Service {
@@ -50,15 +57,45 @@ public class XmppConnectionService extends Service {
 		@Override
 		public void onMessagePacketReceived(Account account,
 				MessagePacket packet) {
+			Conversation conversation = null;
+			String fullJid = packet.getFrom();
+			String counterPart = null;
 			if (packet.getType() == MessagePacket.TYPE_CHAT) {
-				String fullJid = packet.getFrom();
 				String jid = fullJid.split("/")[0];
-				String name = jid.split("@")[0];
-				Contact contact = new Contact(account, name, jid, null); // dummy
-																			// contact
-				Conversation conversation = findOrCreateConversation(account,
-						contact);
-				Message message = new Message(conversation, fullJid,
+				counterPart = fullJid;
+				Contact contact = findOrCreateContact(account,jid);
+				conversation = findOrCreateConversation(account, contact);
+			} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
+				String[] fromParts = fullJid.split("/");
+				if (fromParts.length != 2) {
+					return;
+				}
+				if (packet.hasChild("subject")) {
+					return;
+				}
+				if (packet.hasChild("delay")) {
+					return;
+				}
+
+				String muc = fromParts[0];
+				counterPart = fromParts[1];
+				if (counterPart.equals(account.getUsername())) {
+					return;
+				}
+				for (int i = 0; i < conversations.size(); ++i) {
+					if (conversations.get(i).getContactJid().equals(muc)) {
+						conversation = conversations.get(i);
+						break;
+					}
+				}
+				if (conversation == null) {
+					Log.d(LOGTAG, "couldnt find muc");
+				}
+
+			}
+			if (conversation != null) {
+				Log.d(LOGTAG, packet.toString());
+				Message message = new Message(conversation, counterPart,
 						packet.getBody(), Message.ENCRYPTION_NONE,
 						Message.STATUS_RECIEVED);
 				conversation.getMessages().add(message);
@@ -75,13 +112,15 @@ public class XmppConnectionService extends Service {
 		}
 	};
 	private OnStatusChanged statusListener = new OnStatusChanged() {
-		
+
 		@Override
 		public void onStatusChanged(Account account) {
-			Log.d(LOGTAG,account.getJid()+" changed status to "+account.getStatus());
 			if (accountChangedListener != null) {
 				accountChangedListener.onAccountListChangedListener();
 			}
+			if (account.getStatus() == Account.STATUS_ONLINE) {
+				connectMultiModeConversations(account);
+			}
 		}
 	};
 
@@ -96,9 +135,11 @@ public class XmppConnectionService extends Service {
 		for (Account account : accounts) {
 			if (!connections.containsKey(account)) {
 				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
-					this.connections.put(account, this.createConnection(account));
+					this.connections.put(account,
+							this.createConnection(account));
 				} else {
-					Log.d(LOGTAG,account.getJid()+": not starting because it's disabled");
+					Log.d(LOGTAG, account.getJid()
+							+ ": not starting because it's disabled");
 				}
 			}
 		}
@@ -110,13 +151,12 @@ public class XmppConnectionService extends Service {
 		databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
 		this.accounts = databaseBackend.getAccounts();
 	}
-	
+
 	public XmppConnection createConnection(Account account) {
 		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
 		XmppConnection connection = new XmppConnection(account, pm);
-		connection
-				.setOnMessagePacketReceivedListener(this.messageListener);
-		connection.setOnStatusChangedListener(this.statusListener );
+		connection.setOnMessagePacketReceivedListener(this.messageListener);
+		connection.setOnStatusChangedListener(this.statusListener);
 		Thread thread = new Thread(connection);
 		thread.start();
 		return connection;
@@ -132,7 +172,11 @@ public class XmppConnectionService extends Service {
 				+ message.getCounterpart());
 		databaseBackend.createMessage(message);
 		MessagePacket packet = new MessagePacket();
-		packet.setType(MessagePacket.TYPE_CHAT);
+		if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
+			packet.setType(MessagePacket.TYPE_CHAT);
+		} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
+			packet.setType(MessagePacket.TYPE_GROUPCHAT);
+		}
 		packet.setTo(message.getCounterpart());
 		packet.setFrom(account.getJid());
 		packet.setBody(message.getBody());
@@ -140,37 +184,109 @@ public class XmppConnectionService extends Service {
 		message.setStatus(Message.STATUS_SEND);
 		databaseBackend.updateMessage(message);
 	}
+	
+	public void getRoster(final OnRosterFetchedListener listener) {
+		List<Contact> contacts = databaseBackend.getContacts();
+		if (listener != null) {
+			listener.onRosterFetched(contacts);
+		}
+	}
 
-	public void getRoster(final Account account,
+	public void updateRoster(final Account account,
 			final OnRosterFetchedListener listener) {
-		IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
-		Element query = new Element("query");
-		query.setAttribute("xmlns", "jabber:iq:roster");
-		query.setAttribute("ver", "");
-		iqPacket.addChild(query);
-		connections.get(account).sendIqPacket(iqPacket,
-				new OnIqPacketReceived() {
-
-					@Override
-					public void onIqPacketReceived(Account account,
-							IqPacket packet) {
-						Element roster = packet.findChild("query");
-						List<Contact> contacts = new ArrayList<Contact>();
-						for (Element item : roster.getChildren()) {
-							String name = item.getAttribute("name");
-							String jid = item.getAttribute("jid");
-							if (name == null) {
-								name = jid.split("@")[0];
+
+		final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>();
+		final List<Contact> contacts = new ArrayList<Contact>();
+
+		final String[] PROJECTION = new String[] {
+				ContactsContract.Data.CONTACT_ID,
+				ContactsContract.Data.DISPLAY_NAME,
+				ContactsContract.Data.PHOTO_THUMBNAIL_URI,
+				ContactsContract.CommonDataKinds.Im.DATA };
+
+		final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
+				+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
+				+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
+				+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
+				+ "\")";
+
+		CursorLoader mCursorLoader = new CursorLoader(this,
+				ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
+				null);
+		mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
+
+			@Override
+			public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) {
+				while (cursor.moveToNext()) {
+					Bundle contact = new Bundle();
+					contact.putInt("phoneid", cursor.getInt(cursor
+							.getColumnIndex(ContactsContract.Data.CONTACT_ID)));
+					contact.putString(
+							"displayname",
+							cursor.getString(cursor
+									.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
+					contact.putString(
+							"photouri",
+							cursor.getString(cursor
+									.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI)));
+					phoneContacts.put(
+							cursor.getString(cursor
+									.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)),
+							contact);
+				}
+				IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
+				Element query = new Element("query");
+				query.setAttribute("xmlns", "jabber:iq:roster");
+				query.setAttribute("ver", "");
+				iqPacket.addChild(query);
+				connections.get(account).sendIqPacket(iqPacket,
+						new OnIqPacketReceived() {
+
+							@Override
+							public void onIqPacketReceived(Account account,
+									IqPacket packet) {
+								Element roster = packet.findChild("query");
+								if (roster != null) {
+									for (Element item : roster.getChildren()) {
+										Contact contact;
+										Log.d(LOGTAG, item.toString());
+										String name = item.getAttribute("name");
+										String jid = item.getAttribute("jid");
+										if (phoneContacts.containsKey(jid)) {
+											Bundle phoneContact = phoneContacts
+													.get(jid);
+											contact = new Contact(
+													account,
+													phoneContact
+															.getString("displayname"),
+													jid,
+													phoneContact
+															.getString("photouri"));
+											contact.setSystemAccount(phoneContact.getInt("phoneid"));
+										} else {
+											if (name == null) {
+												name = jid.split("@")[0];
+											}
+											contact = new Contact(account,
+													name, jid, null);
+
+										}
+										contact.setAccount(account);
+										contact.setSubscription(item
+												.getAttribute("subscription"));
+										contacts.add(contact);
+									}
+									databaseBackend.mergeContacts(contacts);
+									if (listener != null) {
+										listener.onRosterFetched(contacts);
+									}
+								}
 							}
-							Contact contact = new Contact(account, name, jid,
-									null);
-							contacts.add(contact);
-						}
-						if (listener != null) {
-							listener.onRosterFetched(contacts);
-						}
-					}
-				});
+						});
+
+			}
+		});
+		mCursorLoader.startLoading();
 	}
 
 	public void addConversation(Conversation conversation) {
@@ -199,6 +315,15 @@ public class XmppConnectionService extends Service {
 	public List<Message> getMessages(Conversation conversation) {
 		return databaseBackend.getMessages(conversation, 100);
 	}
+	
+	public Contact findOrCreateContact(Account account, String jid) {
+		Contact contact = databaseBackend.findContact(account,jid);
+		if (contact!=null) {
+			return contact;
+		} else {
+			return new Contact(account,jid.split("@")[0], jid, null);
+		}
+	}
 
 	public Conversation findOrCreateConversation(Account account,
 			Contact contact) {
@@ -220,7 +345,8 @@ public class XmppConnectionService extends Service {
 		} else {
 			Log.d(LOGTAG, "didnt find one in archive. create new one");
 			conversation = new Conversation(contact.getDisplayName(),
-					contact.getProfilePhoto(), account, contact.getJid());
+					contact.getProfilePhoto(), account, contact.getJid(),
+					Conversation.MODE_SINGLE);
 			this.databaseBackend.createConversation(conversation);
 		}
 		this.conversations.add(conversation);
@@ -246,7 +372,8 @@ public class XmppConnectionService extends Service {
 		databaseBackend.createAccount(account);
 		this.accounts.add(account);
 		this.connections.put(account, this.createConnection(account));
-		if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
+		if (accountChangedListener != null)
+			accountChangedListener.onAccountListChangedListener();
 	}
 
 	public void updateAccount(Account account) {
@@ -259,21 +386,24 @@ public class XmppConnectionService extends Service {
 		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
 			this.connections.put(account, this.createConnection(account));
 		} else {
-			Log.d(LOGTAG,account.getJid()+": not starting because it's disabled");
+			Log.d(LOGTAG, account.getJid()
+					+ ": not starting because it's disabled");
 		}
-		if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
+		if (accountChangedListener != null)
+			accountChangedListener.onAccountListChangedListener();
 	}
 
 	public void deleteAccount(Account account) {
-		Log.d(LOGTAG,"called delete account");
+		Log.d(LOGTAG, "called delete account");
 		if (this.connections.containsKey(account)) {
-			Log.d(LOGTAG,"found connection. disconnecting");
+			Log.d(LOGTAG, "found connection. disconnecting");
 			this.connections.get(account).disconnect();
 			this.connections.remove(account);
 			this.accounts.remove(account);
 		}
 		databaseBackend.deleteAccount(account);
-		if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
+		if (accountChangedListener != null)
+			accountChangedListener.onAccountListChangedListener();
 	}
 
 	public void setOnConversationListChangedListener(
@@ -284,12 +414,38 @@ public class XmppConnectionService extends Service {
 	public void removeOnConversationListChangedListener() {
 		this.convChangedListener = null;
 	}
-	
-	public void setOnAccountListChangedListener(OnAccountListChangedListener listener) {
+
+	public void setOnAccountListChangedListener(
+			OnAccountListChangedListener listener) {
 		this.accountChangedListener = listener;
 	}
-	
+
 	public void removeOnAccountListChangedListener() {
 		this.accountChangedListener = null;
 	}
+
+	public void connectMultiModeConversations(Account account) {
+		List<Conversation> conversations = getConversations();
+		for (int i = 0; i < conversations.size(); i++) {
+			Conversation conversation = conversations.get(i);
+			if ((conversation.getMode() == Conversation.MODE_MULTI)
+					&& (conversation.getAccount() == account)) {
+				String muc = conversation.getContactJid();
+				Log.d(LOGTAG,
+						"join muc " + muc + " with account " + account.getJid());
+				PresencePacket packet = new PresencePacket();
+				packet.setAttribute("to", muc + "/" + account.getUsername());
+				Element x = new Element("x");
+				x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
+				packet.addChild(x);
+				connections.get(conversation.getAccount()).sendPresencePacket(
+						packet);
+
+			}
+		}
+	}
+
+	public void disconnectMultiModeConversations() {
+
+	}
 }

src/de/gultsch/chat/ui/ConversationActivity.java 🔗

@@ -283,17 +283,28 @@ public class ConversationActivity extends XmppActivity {
 		super.onStart();
 		NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 		nm.cancelAll();
+		if (conversationList.size()>=1) {
+			onConvChanged.onConversationListChanged();
+		}
 	}
 	
-	@Override
+	/*@Override
 	protected void onPause() {
 		super.onPause();
 		if (xmppConnectionServiceBound) {
-        	Log.d("xmppService","called on stop. remove listener");
         	xmppConnectionService.removeOnConversationListChangedListener();
             unbindService(mConnection);
             xmppConnectionServiceBound = false;
         }
+	}*/
+	
+	@Override
+	protected void onStop() {
+		Log.d("gultsch","called on stop in conversation activity");
+		if (xmppConnectionServiceBound) {
+        	xmppConnectionService.removeOnConversationListChangedListener();
+		}
+		super.onStop();
 	}
 
 	@Override
@@ -302,7 +313,6 @@ public class ConversationActivity extends XmppActivity {
 		xmppConnectionService.setOnConversationListChangedListener(this.onConvChanged);
 		
 		if (conversationList.size()==0) {
-			Log.d("gultsch","conversation list is empty fetch new");
 			conversationList.clear();
 			conversationList.addAll(xmppConnectionService
 					.getConversations());

src/de/gultsch/chat/ui/ConversationFragment.java 🔗

@@ -115,11 +115,19 @@ public class ConversationFragment extends Fragment {
 				}
 				ImageView imageView = (ImageView) view.findViewById(R.id.message_photo);
 				if (type == RECIEVED) {
-					Uri uri = item.getConversation().getProfilePhotoUri();
-					if (uri!=null) {
-						imageView.setImageURI(uri);
-					} else {
-						imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200));
+					if(item.getConversation().getMode()==Conversation.MODE_SINGLE) {
+						Uri uri = item.getConversation().getProfilePhotoUri();
+						if (uri!=null) {
+							imageView.setImageURI(uri);
+						} else {
+							imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200));
+						}
+					} else if (item.getConversation().getMode()==Conversation.MODE_MULTI) {
+						if (item.getCounterpart()!=null) {
+							imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getCounterpart(), 200));
+						} else {
+							imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200));
+						}
 					}
 				} else {
 					imageView.setImageURI(profilePicture);
@@ -152,12 +160,9 @@ public class ConversationFragment extends Fragment {
 
 		final ConversationActivity activity = (ConversationActivity) getActivity();
 		
-		// TODO check if bond and get data back
-		
 		if (activity.xmppConnectionServiceBound) {
 			this.conversation = activity.getConversationList().get(activity.getSelectedConversation());
-			this.messageList.clear();
-			this.messageList.addAll(this.conversation.getMessages());
+			updateMessages();
 			// rendering complete. now go tell activity to close pane
 			if (!activity.shouldPaneBeOpen()) {
 				activity.getSlidingPaneLayout().closePane();
@@ -165,18 +170,14 @@ public class ConversationFragment extends Fragment {
 				activity.getActionBar().setTitle(conversation.getName());
 				activity.invalidateOptionsMenu();
 			}
-			
-			int size = this.messageList.size();
-			if (size >= 1)
-				messagesView.setSelection(size - 1);
 		}
 	}
 	
 	public void onBackendConnected() {
+		Log.d("gultsch","calling on backend connected in conversation fragment");
 		final ConversationActivity activity = (ConversationActivity) getActivity();
 		this.conversation = activity.getConversationList().get(activity.getSelectedConversation());
-		this.messageList.clear();
-		this.messageList.addAll(this.conversation.getMessages());
+		updateMessages();
 		// rendering complete. now go tell activity to close pane
 		if (!activity.shouldPaneBeOpen()) {
 			activity.getSlidingPaneLayout().closePane();
@@ -190,5 +191,8 @@ public class ConversationFragment extends Fragment {
 		this.messageList.clear();
 		this.messageList.addAll(this.conversation.getMessages());
 		this.messageListAdapter.notifyDataSetChanged();
+		int size = this.messageList.size();
+		if (size >= 1)
+			messagesView.setSelection(size - 1);
 	}
 }

src/de/gultsch/chat/ui/ManageAccountActivity.java 🔗

@@ -152,12 +152,10 @@ public class ManageAccountActivity extends XmppActivity implements ActionMode.Ca
 
 	@Override
 	protected void onStop() {
-		super.onStop();
 		if (xmppConnectionServiceBound) {
 			xmppConnectionService.removeOnAccountListChangedListener();
-			unbindService(mConnection);
-			xmppConnectionServiceBound = false;
 		}
+		super.onStop();
 	}
 
 	@Override

src/de/gultsch/chat/ui/NewConversationActivity.java 🔗

@@ -57,10 +57,6 @@ public class NewConversationActivity extends XmppActivity {
 	protected void updateAggregatedContacts() {
 
 		aggregatedContacts.clear();
-		for (Contact contact : phoneContacts) {
-			if (contact.match(searchString))
-				aggregatedContacts.add(contact);
-		}
 		for (Contact contact : rosterContacts) {
 			if (contact.match(searchString))
 				aggregatedContacts.add(contact);
@@ -71,7 +67,8 @@ public class NewConversationActivity extends XmppActivity {
 			@SuppressLint("DefaultLocale")
 			@Override
 			public int compare(Contact lhs, Contact rhs) {
-				return lhs.getDisplayName().toLowerCase().compareTo(rhs.getDisplayName().toLowerCase());
+				return lhs.getDisplayName().toLowerCase()
+						.compareTo(rhs.getDisplayName().toLowerCase());
 			}
 		});
 
@@ -79,7 +76,7 @@ public class NewConversationActivity extends XmppActivity {
 
 			if (Validator.isValidJid(searchString)) {
 				String name = searchString.split("@")[0];
-				Contact newContact = new Contact(null,name, searchString,null);
+				Contact newContact = new Contact(null, name, searchString, null);
 				aggregatedContacts.add(newContact);
 				contactsHeader.setText("Create new contact");
 			} else {
@@ -93,19 +90,6 @@ public class NewConversationActivity extends XmppActivity {
 		contactsView.setScrollX(0);
 	}
 
-	static final String[] PROJECTION = new String[] {
-			ContactsContract.Data.CONTACT_ID,
-			ContactsContract.Data.DISPLAY_NAME,
-			ContactsContract.Data.PHOTO_THUMBNAIL_URI,
-			ContactsContract.CommonDataKinds.Im.DATA };
-
-	// This is the select criteria
-	static final String SELECTION = "(" + ContactsContract.Data.MIMETYPE
-			+ "=\"" + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
-			+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
-			+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
-			+ "\")";
-
 	@Override
 	protected void onCreate(Bundle savedInstanceState) {
 
@@ -154,11 +138,13 @@ public class NewConversationActivity extends XmppActivity {
 				((TextView) view.findViewById(R.id.contact_jid))
 						.setText(getItem(position).getJid());
 				String profilePhoto = getItem(position).getProfilePhoto();
-				ImageView imageView = (ImageView) view.findViewById(R.id.contact_photo);
-				if (profilePhoto!=null) {
+				ImageView imageView = (ImageView) view
+						.findViewById(R.id.contact_photo);
+				if (profilePhoto != null) {
 					imageView.setImageURI(Uri.parse(profilePhoto));
 				} else {
-					imageView.setImageBitmap(UIHelper.getUnknownContactPicture(getItem(position).getDisplayName(),90));
+					imageView.setImageBitmap(UIHelper.getUnknownContactPicture(
+							getItem(position).getDisplayName(), 90));
 				}
 				return view;
 			}
@@ -168,25 +154,27 @@ public class NewConversationActivity extends XmppActivity {
 		contactsView.setOnItemClickListener(new OnItemClickListener() {
 
 			@Override
-			public void onItemClick(AdapterView<?> arg0, final View view, int pos,
-					long arg3) {
+			public void onItemClick(AdapterView<?> arg0, final View view,
+					int pos, long arg3) {
 				final Contact clickedContact = aggregatedContacts.get(pos);
 				Log.d("gultsch",
 						"clicked on " + clickedContact.getDisplayName());
-				
-				final List<Account> accounts = xmppConnectionService.getAccounts();
+
+				final List<Account> accounts = xmppConnectionService
+						.getAccounts();
 				if (accounts.size() == 1) {
 					startConversation(clickedContact, accounts.get(0));
 				} else {
 					String[] accountList = new String[accounts.size()];
-					for(int i = 0; i < accounts.size(); ++i) {
+					for (int i = 0; i < accounts.size(); ++i) {
 						accountList[i] = accounts.get(i).getJid();
 					}
-	
-					AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+
+					AlertDialog.Builder builder = new AlertDialog.Builder(
+							activity);
 					builder.setTitle("Choose account");
-					builder.setItems(accountList,new OnClickListener() {
-						
+					builder.setItems(accountList, new OnClickListener() {
+
 						@Override
 						public void onClick(DialogInterface dialog, int which) {
 							Account account = accounts.get(which);
@@ -198,56 +186,22 @@ public class NewConversationActivity extends XmppActivity {
 			}
 		});
 	}
-	
+
 	public void startConversation(Contact contact, Account account) {
 		Conversation conversation = xmppConnectionService
 				.findOrCreateConversation(account, contact);
 
-		Intent viewConversationIntent = new Intent(this,ConversationActivity.class);
+		Intent viewConversationIntent = new Intent(this,
+				ConversationActivity.class);
 		viewConversationIntent.setAction(Intent.ACTION_VIEW);
-		viewConversationIntent.putExtra(
-				ConversationActivity.CONVERSATION,
+		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
 				conversation.getUuid());
-		viewConversationIntent
-				.setType(ConversationActivity.VIEW_CONVERSATION);
-		viewConversationIntent.setFlags(viewConversationIntent
-				.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
+		viewConversationIntent.setFlags(viewConversationIntent.getFlags()
+				| Intent.FLAG_ACTIVITY_CLEAR_TOP);
 		startActivity(viewConversationIntent);
 	}
 
-	@Override
-	public void onStart() {
-		super.onStart();
-
-		CursorLoader mCursorLoader = new CursorLoader(this,
-				ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
-				null);
-		mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
-
-			@Override
-			public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) {
-				phoneContacts.clear();
-				while (cursor.moveToNext()) {
-					String profilePhoto = cursor.getString(cursor
-							.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI));
-					/*if (profilePhoto == null) {
-						profilePhoto = DEFAULT_PROFILE_PHOTO;
-					}*/
-					Contact contact = new Contact(null,
-							cursor.getString(cursor
-									.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)),
-							cursor.getString(cursor
-									.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)),
-							profilePhoto);
-					phoneContacts.add(contact);
-				}
-				updateAggregatedContacts();
-			}
-		});
-		mCursorLoader.startLoading();
-
-	}
-
 	@Override
 	void onBackendConnected() {
 		if (xmppConnectionService.getConversationCount() == 0) {
@@ -256,23 +210,21 @@ public class NewConversationActivity extends XmppActivity {
 		}
 		this.accounts = xmppConnectionService.getAccounts();
 		this.rosterContacts.clear();
-		for(Account account : this.accounts) {
-			xmppConnectionService.getRoster(account, new OnRosterFetchedListener() {
-				
-				@Override
-				public void onRosterFetched(List<Contact> roster) {
-					rosterContacts.addAll(roster);
-					runOnUiThread(new Runnable() {
-						
-						@Override
-						public void run() {
-							updateAggregatedContacts();
-						}
-					});
-	
-				}
-			});
-		}
+		xmppConnectionService.getRoster(new OnRosterFetchedListener() {
+
+			@Override
+			public void onRosterFetched(List<Contact> roster) {
+				rosterContacts.addAll(roster);
+				runOnUiThread(new Runnable() {
+
+					@Override
+					public void run() {
+						updateAggregatedContacts();
+					}
+				});
+
+			}
+		});
 	}
 
 	@Override

src/de/gultsch/chat/utils/UIHelper.java 🔗

@@ -46,13 +46,11 @@ public class UIHelper {
 
 	public static Bitmap getUnknownContactPicture(String name, int size) {
 		String firstLetter = name.substring(0, 1).toUpperCase();
-		String centerLetter = name.substring(name.length() / 2,
-				(name.length() / 2) + 1);
 
 		int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713,
 				0xFFe92727 };
 
-		int color = holoColors[centerLetter.charAt(0) % holoColors.length];
+		int color = holoColors[Math.abs(name.hashCode()) % holoColors.length];
 
 		Bitmap bitmap = Bitmap
 				.createBitmap(size, size, Bitmap.Config.ARGB_8888);

src/de/gultsch/chat/xmpp/MessagePacket.java 🔗

@@ -6,6 +6,7 @@ public class MessagePacket extends Element {
 	public static final int TYPE_CHAT = 0;
 	public static final int TYPE_UNKNOWN = 1;
 	public static final int TYPE_NO = 2;
+	public static final int TYPE_GROUPCHAT = 3;
 
 	private MessagePacket(String name) {
 		super(name);
@@ -51,7 +52,9 @@ public class MessagePacket extends Element {
 		case TYPE_CHAT:
 			this.setAttribute("type","chat");
 			break;
-
+		case TYPE_GROUPCHAT:
+			this.setAttribute("type", "groupchat");
+			break;
 		default:
 			this.setAttribute("type","chat");
 			break;
@@ -65,6 +68,8 @@ public class MessagePacket extends Element {
 		}
 		if (type.equals("chat")) {
 			return TYPE_CHAT;
+		} else if (type.equals("groupchat")) {
+			return TYPE_GROUPCHAT;
 		} else {
 			return TYPE_UNKNOWN;
 		}

src/de/gultsch/chat/xmpp/XmppConnection.java 🔗

@@ -340,6 +340,7 @@ public class XmppConnection implements Runnable {
 	
 	public void sendPresencePacket(PresencePacket packet)  {
 		tagWriter.writeElement(packet);
+		Log.d(LOGTAG,account.getJid()+": sending: "+packet.toString());
 	}
 	
 	public void setOnMessagePacketReceivedListener(OnMessagePacketReceived listener) {