muc creation

Daniel Gultsch created

Change summary

res/drawable-hdpi/ic_action_chat.png                           |   0 
res/drawable-mdpi/ic_action_chat.png                           |   0 
res/drawable-xhdpi/ic_action_chat.png                          |   0 
res/drawable-xxhdpi/ic_action_chat.png                         |   0 
res/layout/activity_new_conversation.xml                       |   3 
res/layout/contact.xml                                         |   3 
res/menu/newconversation_context.xml                           |  14 
res/values/strings.xml                                         |   1 
src/eu/siacs/conversations/services/XmppConnectionService.java |  39 
src/eu/siacs/conversations/ui/ConversationFragment.java        |   7 
src/eu/siacs/conversations/ui/MucDetailsActivity.java          |   2 
src/eu/siacs/conversations/ui/NewConversationActivity.java     | 197 +++
src/eu/siacs/conversations/xmpp/XmppConnection.java            |  49 
src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java     |   9 
14 files changed, 273 insertions(+), 51 deletions(-)

Detailed changes

res/layout/activity_new_conversation.xml 🔗

@@ -49,7 +49,8 @@
         android:layout_alignParentLeft="true"
         android:layout_alignParentRight="true"
         android:layout_below="@+id/contacts_header"
-        tools:listitem="@layout/contact" >
+        tools:listitem="@layout/contact"
+        android:choiceMode="multipleChoice">
 
     </ListView>
 </RelativeLayout>

res/layout/contact.xml 🔗

@@ -3,7 +3,8 @@
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:padding="8dp"
-    android:paddingBottom="8dp">
+    android:paddingBottom="8dp"
+    android:background="?android:attr/activatedBackgroundIndicator">
 
     <ImageView
         android:id="@+id/contact_photo"

res/menu/newconversation_context.xml 🔗

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    
+    <item
+        android:id="@+id/action_contact_details"
+        android:showAsAction="ifRoom"
+        android:icon="@drawable/ic_action_person"
+        android:title="@string/action_contact_details" />
+ <item
+        android:id="@+id/action_start_conversation"
+        android:showAsAction="ifRoom"
+        android:icon="@drawable/ic_action_chat"
+        android:title="@string/start_conversation" />
+</menu>

res/values/strings.xml 🔗

@@ -29,4 +29,5 @@
     <string name="ask_again"><u>Click to ask again</u></string>
     <string name="show_otr_key">OTR fingerprint</string>
     <string name="no_otr_fingerprint">No OTR Fingerprint generated. Just go ahead an start an encrypted conversation</string>
+    <string name="start_conversation">Start Conversation</string>
 </resources>

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -148,8 +148,20 @@ public class XmppConnectionService extends Service {
 				}
 			} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
 				message = MessageParser.parseError(packet, account, service);
-			} else {
-				// Log.d(LOGTAG, "unparsed message " + packet.toString());
+			} else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
+				if (packet.hasChild("x")) {
+					Element x = packet.findChild("x");
+					if (x.hasChild("invite")) {
+						findOrCreateConversation(account, packet.getFrom(), true);
+						if (convChangedListener != null) {
+							convChangedListener.onConversationListChanged();
+						}
+						Log.d(LOGTAG,"invitation received to "+packet.getFrom());
+					}
+					
+				} else {
+					Log.d(LOGTAG, "unparsed message " + packet.toString());
+				}
 			}
 			if ((message == null)||(message.getBody() == null)) {
 				return;
@@ -223,7 +235,7 @@ public class XmppConnectionService extends Service {
 					&& (packet.findChild("x").getAttribute("xmlns")
 							.startsWith("http://jabber.org/protocol/muc"))) {
 				Conversation muc = findMuc(packet.getAttribute("from").split(
-						"/")[0]);
+						"/")[0],account);
 				if (muc != null) {
 					int error = muc.getMucOptions().getError();
 					muc.getMucOptions().processPacket(packet);
@@ -336,9 +348,9 @@ public class XmppConnectionService extends Service {
 
 	}
 
-	protected Conversation findMuc(String name) {
+	protected Conversation findMuc(String name, Account account) {
 		for (Conversation conversation : this.conversations) {
-			if (conversation.getContactJid().split("/")[0].equals(name)) {
+			if (conversation.getContactJid().split("/")[0].equals(name)&&(conversation.getAccount() == account)) {
 				return conversation;
 			}
 		}
@@ -1246,4 +1258,21 @@ public class XmppConnectionService extends Service {
 			account.getXmppConnection().sendMessagePacket(packet);
 		}
 	}
+
+	public void inviteToConference(Conversation conversation,
+			List<Contact> contacts) {
+		for(Contact contact : contacts) {
+			MessagePacket packet = new MessagePacket();
+			packet.setTo(conversation.getContactJid().split("/")[0]);
+			packet.setFrom(conversation.getAccount().getFullJid());
+			Element x = new Element("x");
+			x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
+			Element invite = new Element("invite");
+			invite.setAttribute("to", contact.getJid());
+			x.addChild(invite);
+			packet.addChild(x);
+			conversation.getAccount().getXmppConnection().sendMessagePacket(packet);
+		}
+		
+	}
 }

src/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -207,12 +207,14 @@ public class ConversationFragment extends Fragment {
 						viewHolder.imageView = (ImageView) view
 								.findViewById(R.id.message_photo);
 						viewHolder.imageView.setImageBitmap(selfBitmap);
+						viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
 						break;
 					case RECIEVED:
 						view = (View) inflater.inflate(
 								R.layout.message_recieved, null);
 						viewHolder.imageView = (ImageView) view
 								.findViewById(R.id.message_photo);
+						viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
 						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
 
 							viewHolder.imageView.setImageBitmap(mBitmapCache
@@ -239,7 +241,6 @@ public class ConversationFragment extends Fragment {
 							.findViewById(R.id.message_body);
 					viewHolder.time = (TextView) view
 							.findViewById(R.id.message_time);
-					viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
 					view.setTag(viewHolder);
 				} else {
 					viewHolder = (ViewHolder) view.getTag();
@@ -279,7 +280,9 @@ public class ConversationFragment extends Fragment {
 						viewHolder.messageBody.setTextColor(0xff000000);
 						viewHolder.messageBody.setTypeface(null,
 								Typeface.NORMAL);
-						viewHolder.indicator.setVisibility(View.GONE);
+						if (item.getStatus() != Message.STATUS_ERROR) {
+							viewHolder.indicator.setVisibility(View.GONE);
+						}
 					}
 				} else {
 					viewHolder.indicator.setVisibility(View.GONE);

src/eu/siacs/conversations/ui/MucDetailsActivity.java 🔗

@@ -40,11 +40,9 @@ public class MucDetailsActivity extends XmppActivity {
 
 		@Override
 		public void onClick(View arg0) {
-			Log.d("gultsch","on click change muc");
 			MucOptions options = conversation.getMucOptions();
 			String nick = mYourNick.getText().toString();
 			if (!options.getNick().equals(nick)) {
-				Log.d("gultsch","call to change muc");
 				xmppConnectionService.renameInMuc(conversation, nick);
 				finish();
 			}

src/eu/siacs/conversations/ui/NewConversationActivity.java 🔗

@@ -1,7 +1,9 @@
 package eu.siacs.conversations.ui;
 
 import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
 import java.net.URLDecoder;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -16,11 +18,16 @@ import eu.siacs.conversations.utils.Validator;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextWatcher;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
 import android.view.LayoutInflater;
 import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.AdapterView.OnItemLongClickListener;
@@ -49,6 +56,110 @@ public class NewConversationActivity extends XmppActivity {
 	protected String searchString = "";
 	private TextView contactsHeader;
 	private List<Account> accounts;
+	private List<Contact> selectedContacts = new ArrayList<Contact>();
+	
+	private boolean isActionMode = false;
+	private ActionMode actionMode = null;
+	private AbsListView.MultiChoiceModeListener actionModeCallback = new AbsListView.MultiChoiceModeListener() {
+		
+		@Override
+		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+			menu.clear();
+			MenuInflater inflater = mode.getMenuInflater();
+	        inflater.inflate(R.menu.newconversation_context, menu);
+	        SparseBooleanArray checkedItems = contactsView.getCheckedItemPositions();
+	        selectedContacts.clear();
+	        for(int i = 0; i < aggregatedContacts.size(); ++i) {
+	        	if (checkedItems.get(i, false)) {
+	        		selectedContacts.add(aggregatedContacts.get(i));
+	        	}
+	        }
+	        if (selectedContacts.size() == 0) {
+	        	menu.findItem(R.id.action_start_conversation).setVisible(false);
+	        	menu.findItem(R.id.action_contact_details).setVisible(false);
+	        } else if (selectedContacts.size() == 1) {
+	        	menu.findItem(R.id.action_start_conversation).setVisible(true);
+	        	menu.findItem(R.id.action_contact_details).setVisible(true);
+	        } else {
+	        	menu.findItem(R.id.action_start_conversation).setVisible(true);
+	        	menu.findItem(R.id.action_contact_details).setVisible(false);
+	        }
+			return true;
+		}
+		
+		@Override
+		public void onDestroyActionMode(ActionMode mode) {
+			// TODO Auto-generated method stub
+			
+		}
+		
+		@Override
+		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+			return true;
+		}
+		
+		@Override
+		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+			switch (item.getItemId()) {
+			case R.id.action_start_conversation:
+				if (selectedContacts.size() == 1) {
+					startConversation(selectedContacts.get(0));
+				} else {
+					startConference();
+				}
+				break;
+			case R.id.action_contact_details:
+				Intent intent = new Intent(getApplicationContext(),ContactDetailsActivity.class);
+				intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
+				intent.putExtra("uuid", selectedContacts.get(0).getUuid());
+				startActivity(intent);
+				break;
+			default:
+				break;
+			}
+			// TODO Auto-generated method stub
+			return false;
+		}
+		
+		@Override
+		public void onItemCheckedStateChanged(ActionMode mode, int position,
+				long id, boolean checked) {
+		}
+	};
+	
+	private void startConference() {
+		if (accounts.size()>1) {
+			getAccountChooser(new OnClickListener() {
+
+				@Override
+				public void onClick(DialogInterface dialog, int which) {
+					startConference(accounts.get(which), selectedContacts);
+				}
+				}).show();
+		} else {
+			startConference(accounts.get(0), selectedContacts);
+		}
+		
+	}
+	
+	private void startConference(Account account, List<Contact> contacts) {
+		SecureRandom random = new SecureRandom();
+		String mucName = new BigInteger(100,random).toString(32);
+		String serverName = account.getXmppConnection().getMucServer();
+		String jid = mucName+"@"+serverName;
+		Conversation conversation = xmppConnectionService.findOrCreateConversation(account, jid , true);
+		StringBuilder subject = new StringBuilder();
+		for(int i = 0; i < selectedContacts.size(); ++i) {
+			if (i+1!=selectedContacts.size()) {
+				subject.append(selectedContacts.get(i).getDisplayName()+", ");
+			} else {
+				subject.append(selectedContacts.get(i).getDisplayName());
+			}
+		}
+		xmppConnectionService.sendConversationSubject(conversation, subject.toString());
+		xmppConnectionService.inviteToConference(conversation, contacts);
+		switchToConversation(conversation, null);
+	}
 
 	protected void updateAggregatedContacts() {
 
@@ -86,6 +197,20 @@ public class NewConversationActivity extends XmppActivity {
 		contactsAdapter.notifyDataSetChanged();
 		contactsView.setScrollX(0);
 	}
+	
+	private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() {
+
+		@Override
+		public boolean onItemLongClick(AdapterView<?> arg0, View view,
+				int position, long arg3) {
+			if (!isActionMode) {
+				contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+				contactsView.setItemChecked(position,true);
+				actionMode = contactsView.startActionMode(actionModeCallback);
+			}
+			return true;
+		}
+	};
 
 	@Override
 	protected void onCreate(Bundle savedInstanceState) {
@@ -143,43 +268,40 @@ public class NewConversationActivity extends XmppActivity {
 			}
 		};
 		contactsView.setAdapter(contactsAdapter);
-		final Activity activity = this;
+		contactsView.setMultiChoiceModeListener(actionModeCallback);
 		contactsView.setOnItemClickListener(new OnItemClickListener() {
 
 			@Override
 			public void onItemClick(AdapterView<?> arg0, final View view,
 					int pos, long arg3) {
-				final Contact clickedContact = aggregatedContacts.get(pos);
-				
-				if ((clickedContact.getAccount()==null)&&(accounts.size()>1)) {
-					getAccountChooser(new OnClickListener() {
-
-						@Override
-						public void onClick(DialogInterface dialog, int which) {
-							clickedContact.setAccount(accounts.get(which));
-							showIsMucDialogIfNeeded(clickedContact);
-						}
-						}).show();
+				if (!isActionMode) {
+					Contact clickedContact = aggregatedContacts.get(pos);
+					startConversation(clickedContact);
+					
 				} else {
-					if (clickedContact.getAccount()==null) {
-						clickedContact.setAccount(accounts.get(0));
-					}
-					showIsMucDialogIfNeeded(clickedContact);
+					actionMode.invalidate();
 				}
 			}
 		});
-		contactsView.setOnItemLongClickListener(new OnItemLongClickListener() {
+		contactsView.setOnItemLongClickListener(this.onLongClickListener);
+	}
+	
+	public void startConversation(final Contact contact) {
+		if ((contact.getAccount()==null)&&(accounts.size()>1)) {
+			getAccountChooser(new OnClickListener() {
 
-			@Override
-			public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
-					int pos, long arg3) {
-				Intent intent = new Intent(activity,ContactDetailsActivity.class);
-				intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
-				intent.putExtra("uuid", aggregatedContacts.get(pos).getUuid());
-				startActivity(intent);
-				return true;
+				@Override
+				public void onClick(DialogInterface dialog, int which) {
+					contact.setAccount(accounts.get(which));
+					showIsMucDialogIfNeeded(contact);
+				}
+				}).show();
+		} else {
+			if (contact.getAccount()==null) {
+				contact.setAccount(accounts.get(0));
 			}
-		});
+			showIsMucDialogIfNeeded(contact);
+		}
 	}
 	
 	protected AlertDialog getAccountChooser(OnClickListener listener) {
@@ -329,4 +451,27 @@ public class NewConversationActivity extends XmppActivity {
 			}
 		}
 	}
+	
+	@Override
+	public void onActionModeStarted(ActionMode mode) {
+		super.onActionModeStarted(mode);
+		this.isActionMode = true;
+		search.setEnabled(false);
+	}
+	
+	@Override
+	public void onActionModeFinished(ActionMode mode) {
+		super.onActionModeFinished(mode);
+		this.isActionMode = false;
+		contactsView.clearChoices();
+		contactsView.requestLayout();
+		contactsView.post(new Runnable() {
+            @Override
+            public void run() {
+                contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE);
+            }
+        });
+		search.setEnabled(true);
+	}
+	
 }

src/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -15,6 +15,7 @@ import java.security.SecureRandom;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
@@ -66,6 +67,7 @@ public class XmppConnection implements Runnable {
 	private boolean shouldAuthenticate = true;
 	private Element streamFeatures;
 	private HashSet<String> discoFeatures = new HashSet<String>();
+	private List<String> discoItems = new ArrayList<String>();
 	
 	private String streamId = null;
 	
@@ -550,7 +552,8 @@ public class XmppConnection implements Runnable {
 					tagWriter.writeStanzaAsync(enable);
 				}
 				sendInitialPresence();
-				sendServiceDiscovery();
+				sendServiceDiscoveryInfo();
+				sendServiceDiscoveryItems();
 				if (statusListener != null) {
 					statusListener.onStatusChanged(account);
 				}
@@ -558,32 +561,45 @@ public class XmppConnection implements Runnable {
 		});
 	}
 
-	private void sendServiceDiscovery() {
+	private void sendServiceDiscoveryInfo() {
 		IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
-		iq.setAttribute("to", account.getServer());
-		Element query = new Element("query");
-		query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info");
-		iq.addChild(query);
+		iq.setTo(account.getServer());
+		iq.query("http://jabber.org/protocol/disco#info");
 		this.sendIqPacket(iq, new OnIqPacketReceived() {
 
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
-				if (packet.hasChild("query")) {
-					List<Element> elements = packet.findChild("query")
-							.getChildren();
+					List<Element> elements = packet.query().getChildren();
 					for (int i = 0; i < elements.size(); ++i) {
 						if (elements.get(i).getName().equals("feature")) {
 							discoFeatures.add(elements.get(i).getAttribute(
 									"var"));
 						}
 					}
-				}
 				if (discoFeatures.contains("urn:xmpp:carbons:2")) {
 					sendEnableCarbons();
 				}
 			}
 		});
 	}
+	private void sendServiceDiscoveryItems() {
+		IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
+		iq.setTo(account.getServer());
+		iq.query("http://jabber.org/protocol/disco#items");
+		this.sendIqPacket(iq, new OnIqPacketReceived() {
+
+			@Override
+			public void onIqPacketReceived(Account account, IqPacket packet) {
+					List<Element> elements = packet.query().getChildren();
+					for (int i = 0; i < elements.size(); ++i) {
+						if (elements.get(i).getName().equals("item")) {
+							discoItems.add(elements.get(i).getAttribute(
+									"jid"));
+						}
+					}
+			}
+		});
+	}
 
 	private void sendEnableCarbons() {
 		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
@@ -754,4 +770,17 @@ public class XmppConnection implements Runnable {
 	public int getSentStanzas() {
 		return this.stanzasSent;
 	}
+
+	public String getMucServer() {
+		for(int i = 0; i < discoItems.size(); ++i) {
+			if (discoItems.get(i).contains("conference.")) {
+				return discoItems.get(i);
+			} else if (discoItems.get(i).contains("conf.")) {
+				return discoItems.get(i);
+			} else if (discoItems.get(i).contains("muc.")) {
+				return discoItems.get(i);
+			}
+		}
+		return null;
+	}
 }

src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java 🔗

@@ -5,7 +5,7 @@ import eu.siacs.conversations.xml.Element;
 public class MessagePacket extends AbstractStanza {
 	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_NORMAL = 2;
 	public static final int TYPE_GROUPCHAT = 3;
 	public static final int TYPE_ERROR = 4;
 	
@@ -46,9 +46,10 @@ public class MessagePacket extends AbstractStanza {
 	public int getType() {
 		String type = getAttribute("type");
 		if (type==null) {
-			return TYPE_NO;
-		}
-		if (type.equals("chat")) {
+			return TYPE_NORMAL;
+		} else if (type.equals("normal")) {
+			return TYPE_NORMAL;
+		} else if (type.equals("chat")) {
 			return TYPE_CHAT;
 		} else if (type.equals("groupchat")) {
 			return TYPE_GROUPCHAT;