better muc invitations. clearified the creation of ad hoc mucs with an alert dialog

Daniel Gultsch created

Change summary

AndroidManifest.xml                                            |   4 
res/menu/conversations.xml                                     |   4 
res/menu/newconversation_context.xml                           |   4 
res/values/strings.xml                                         |  11 
src/eu/siacs/conversations/services/XmppConnectionService.java |  30 
src/eu/siacs/conversations/ui/ContactsActivity.java            | 134 +++
src/eu/siacs/conversations/ui/ConversationActivity.java        |  17 
src/eu/siacs/conversations/ui/ConversationFragment.java        |   3 
src/eu/siacs/conversations/ui/ManageAccountActivity.java       |   2 
src/eu/siacs/conversations/ui/MucDetailsActivity.java          |   2 
src/eu/siacs/conversations/utils/CryptoHelper.java             |  22 
src/eu/siacs/conversations/xmpp/XmppConnection.java            |  10 
12 files changed, 184 insertions(+), 59 deletions(-)

Detailed changes

AndroidManifest.xml 🔗

@@ -62,8 +62,8 @@
             android:windowSoftInputMode="stateHidden" >
         </activity>
         <activity
-            android:name="eu.siacs.conversations.ui.NewConversationActivity"
-            android:label="@string/title_activity_new_conversation"
+            android:name="eu.siacs.conversations.ui.ContactsActivity"
+            android:label="@string/title_activity_contacts"
             android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity"
             android:windowSoftInputMode="stateHidden" >
             <meta-data

res/menu/conversations.xml 🔗

@@ -26,6 +26,10 @@
         android:showAsAction="ifRoom"
         android:icon="@drawable/ic_action_group"
         android:title="@string/action_muc_details" />
+       <item
+        android:id="@+id/action_invite"
+        android:showAsAction="never"
+        android:title="@string/invite_contacts" />
     
     <item
         android:id="@+id/action_archive"

res/menu/newconversation_context.xml 🔗

@@ -15,4 +15,8 @@
         android:id="@+id/action_invite"
         android:showAsAction="ifRoom"
         android:title="@string/invite_contacts" />
+  <item
+        android:id="@+id/action_invite_to_existing"
+        android:showAsAction="never"
+        android:title="@string/invite_contacts_to_existing" />
 </menu>

res/values/strings.xml 🔗

@@ -10,7 +10,7 @@
     <string name="action_muc_details">Conferenece details</string>
     <string name="action_secure">Secure conversation</string>
     <string name="action_add_account">Add account</string>
-    <string name="title_activity_new_conversation">New Conversation</string>
+    <string name="title_activity_contacts">Contacts</string>
     <string name="just_now">just now</string>
     <string name="sending">sending&#8230;</string>
     <string name="announce_pgp">Renew PGP announcement</string>
@@ -31,4 +31,13 @@
     <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>
     <string name="invite_contacts">Invite Contacts</string>
+    <string name="invite_contacts_to_existing">Invite to existing conference</string>
+    <string name="new_conference">Create new conference</string>
+    <string name="cancel">Cancel</string>
+    <string name="create_invite">Create \u0026 Invite</string>
+    <string name="new_conference_explained">Do you want to create a new conference with a randomly generated address and invite the selected contacts to it?</string>
+    <string name="no_open_mucs">No existing conferences</string>
+    <string name="invitation_sent">Invitation sent</string>
+    <string name="account_offline">Account offline</string>
+    <string name="cant_invite_while_offline">You have to be online to invite people to conferences</string>
 </resources>

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

@@ -72,7 +72,7 @@ public class XmppConnectionService extends Service {
 
 	private static final int PING_MAX_INTERVAL = 300;
 	private static final int PING_MIN_INTERVAL = 10;
-	private static final int PING_TIMEOUT = 2;
+	private static final int PING_TIMEOUT = 5;
 	private static final int CONNECT_TIMEOUT = 60;
 
 	private List<Account> accounts;
@@ -160,7 +160,7 @@ public class XmppConnectionService extends Service {
 					}
 					
 				} else {
-					Log.d(LOGTAG, "unparsed message " + packet.toString());
+					//Log.d(LOGTAG, "unparsed message " + packet.toString());
 				}
 			}
 			if ((message == null)||(message.getBody() == null)) {
@@ -199,19 +199,6 @@ public class XmppConnectionService extends Service {
 				accountChangedListener.onAccountListChangedListener();
 			}
 			if (account.getStatus() == Account.STATUS_ONLINE) {
-				if (account.getXmppConnection().hasFeatureRosterManagment()) {
-					updateRoster(account, null);
-				}
-				connectMultiModeConversations(account);
-				List<Conversation> conversations = getConversations();
-				for (int i = 0; i < conversations.size(); ++i) {
-					if (conversations.get(i).getAccount() == account) {
-						sendUnsendMessages(conversations.get(i));
-					}
-				}
-				if (convChangedListener != null) {
-					convChangedListener.onConversationListChanged();
-				}
 				scheduleWakeupCall(PING_MAX_INTERVAL, true);
 			} else if (account.getStatus() == Account.STATUS_OFFLINE) {
 				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
@@ -558,6 +545,19 @@ public class XmppConnectionService extends Service {
 			@Override
 			public void onBind(Account account) {
 				databaseBackend.clearPresences(account);
+				if (account.getXmppConnection().hasFeatureRosterManagment()) {
+					updateRoster(account, null);
+				}
+				connectMultiModeConversations(account);
+				List<Conversation> conversations = getConversations();
+				for (int i = 0; i < conversations.size(); ++i) {
+					if (conversations.get(i).getAccount() == account) {
+						sendUnsendMessages(conversations.get(i));
+					}
+				}
+				if (convChangedListener != null) {
+					convChangedListener.onConversationListChanged();
+				}
 			}
 		});
 		return connection;

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

@@ -1,9 +1,7 @@
 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;
@@ -13,9 +11,11 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.utils.Validator;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.util.Log;
@@ -37,14 +37,17 @@ import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.ImageView;
+import android.widget.Toast;
 import android.annotation.SuppressLint;
 import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.SharedPreferences;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 
-public class NewConversationActivity extends XmppActivity {
+public class ContactsActivity extends XmppActivity {
 
 	protected List<Contact> rosterContacts = new ArrayList<Contact>();
 	protected List<Contact> aggregatedContacts = new ArrayList<Contact>();
@@ -56,7 +59,10 @@ public class NewConversationActivity extends XmppActivity {
 	private TextView contactsHeader;
 	private List<Account> accounts;
 	private List<Contact> selectedContacts = new ArrayList<Contact>();
+	
+	private ContactsActivity activity = this;
 
+	private boolean useSubject = true;
 	private boolean isActionMode = false;
 	private boolean inviteIntent = false;
 	private ActionMode actionMode = null;
@@ -79,18 +85,22 @@ public class NewConversationActivity extends XmppActivity {
 				menu.findItem(R.id.action_start_conversation).setVisible(false);
 				menu.findItem(R.id.action_contact_details).setVisible(false);
 				menu.findItem(R.id.action_invite).setVisible(false);
-			} else if ((selectedContacts.size() == 1)&&(!inviteIntent)) {
+				menu.findItem(R.id.action_invite_to_existing).setVisible(false);
+			} else if ((selectedContacts.size() == 1) && (!inviteIntent)) {
 				menu.findItem(R.id.action_start_conversation).setVisible(true);
 				menu.findItem(R.id.action_contact_details).setVisible(true);
 				menu.findItem(R.id.action_invite).setVisible(false);
-			} else if (!inviteIntent){
+				menu.findItem(R.id.action_invite_to_existing).setVisible(true);
+			} else if (!inviteIntent) {
 				menu.findItem(R.id.action_start_conversation).setVisible(true);
 				menu.findItem(R.id.action_contact_details).setVisible(false);
 				menu.findItem(R.id.action_invite).setVisible(false);
+				menu.findItem(R.id.action_invite_to_existing).setVisible(true);
 			} else {
 				menu.findItem(R.id.action_invite).setVisible(true);
 				menu.findItem(R.id.action_start_conversation).setVisible(false);
 				menu.findItem(R.id.action_contact_details).setVisible(false);
+				menu.findItem(R.id.action_invite_to_existing).setVisible(false);
 			}
 			return true;
 		}
@@ -125,11 +135,42 @@ public class NewConversationActivity extends XmppActivity {
 				break;
 			case R.id.action_invite:
 				invite();
-		break;
+				break;
+			case R.id.action_invite_to_existing:
+				final List<Conversation> mucs = new ArrayList<Conversation>();
+				for(Conversation conv : xmppConnectionService.getConversations()) {
+					if (conv.getMode() == Conversation.MODE_MULTI) {
+						mucs.add(conv);
+					}
+				}
+				AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+				builder.setTitle(getString(R.string.invite_contacts_to_existing));
+				if (mucs.size() >= 1) {
+					String[] options = new String[mucs.size()];
+					for(int i = 0; i < options.length; ++i) {
+						options[i] = mucs.get(i).getName(useSubject);
+					}
+					builder.setItems(options, new OnClickListener() {
+						
+						@Override
+						public void onClick(DialogInterface dialog, int which) {
+							Conversation conversation = mucs.get(which);
+							if (isOnline(conversation.getAccount())) {
+								xmppConnectionService.inviteToConference(conversation, selectedContacts);
+								Toast.makeText(activity, getString(R.string.invitation_sent), Toast.LENGTH_SHORT).show();
+								actionMode.finish();
+							}
+						}
+					});
+				} else {
+					builder.setMessage(getString(R.string.no_open_mucs));
+				}
+				builder.setNegativeButton(getString(R.string.cancel),null);
+				builder.create().show();
+				break;
 			default:
 				break;
 			}
-			// TODO Auto-generated method stub
 			return false;
 		}
 
@@ -139,6 +180,20 @@ public class NewConversationActivity extends XmppActivity {
 		}
 	};
 
+	private boolean isOnline(Account account) {
+		if (account.getStatus() == Account.STATUS_ONLINE) {
+			return true;
+		} else {
+			AlertDialog.Builder builder = new AlertDialog.Builder(this);
+			builder.setTitle(getString(R.string.account_offline));
+			builder.setMessage(getString(R.string.cant_invite_while_offline));
+			builder.setNegativeButton("OK", null);
+			builder.setIconAttribute(android.R.attr.alertDialogIcon);
+			builder.create().show();
+			return false;
+		}
+	}
+	
 	private void invite() {
 		List<Conversation> conversations = xmppConnectionService
 				.getConversations();
@@ -151,45 +206,64 @@ public class NewConversationActivity extends XmppActivity {
 			}
 		}
 		if (conversation != null) {
-			xmppConnectionService.inviteToConference(conversation, selectedContacts);
+			xmppConnectionService.inviteToConference(conversation,
+					selectedContacts);
 		}
 		finish();
 	}
-	
+
 	private void startConference() {
 		if (accounts.size() > 1) {
 			getAccountChooser(new OnClickListener() {
 
 				@Override
 				public void onClick(DialogInterface dialog, int which) {
-					startConference(accounts.get(which), selectedContacts);
+					startConference(accounts.get(which));
 				}
 			}).show();
 		} else {
-			startConference(accounts.get(0), selectedContacts);
+			startConference(accounts.get(0));
 		}
 
 	}
 
-	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());
-			}
+	private void startConference(final Account account) {
+		if (isOnline(account)) {
+			AlertDialog.Builder builder = new AlertDialog.Builder(this);
+			builder.setTitle(getString(R.string.new_conference));
+			builder.setMessage(getString(R.string.new_conference_explained));
+			builder.setNegativeButton(getString(R.string.cancel), null);
+			builder.setPositiveButton(getString(R.string.create_invite),
+					new OnClickListener() {
+	
+						@Override
+						public void onClick(DialogInterface dialog, int which) {
+							String mucName = CryptoHelper.randomMucName();
+							String serverName = account.getXmppConnection()
+									.getMucServer();
+							String jid = mucName + "@" + serverName;
+							Conversation conversation = xmppConnectionService
+									.findOrCreateConversation(account, jid, true);
+							StringBuilder subject = new StringBuilder();
+							subject.append(account.getUsername() + ", ");
+							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,
+									selectedContacts);
+							switchToConversation(conversation, null);
+						}
+					});
+			builder.create().show();
 		}
-		xmppConnectionService.sendConversationSubject(conversation,
-				subject.toString());
-		xmppConnectionService.inviteToConference(conversation, contacts);
-		switchToConversation(conversation, null);
 	}
 
 	protected void updateAggregatedContacts() {
@@ -246,6 +320,8 @@ public class NewConversationActivity extends XmppActivity {
 	@Override
 	protected void onStart() {
 		super.onStart();
+		SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
+		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
 		inviteIntent = "invite".equals(getIntent().getAction());
 		if (inviteIntent) {
 			contactsHeader.setVisibility(View.GONE);

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

@@ -74,7 +74,7 @@ public class ConversationActivity extends XmppActivity {
 						if (conversationList.size() >= 1) {
 							swapConversationFragment();
 						} else {
-							startActivity(new Intent(getApplicationContext(), NewConversationActivity.class));
+							startActivity(new Intent(getApplicationContext(), ContactsActivity.class));
 							finish();
 						}
 					}
@@ -249,12 +249,14 @@ public class ConversationActivity extends XmppActivity {
 		MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
 		MenuItem menuMucDetails = (MenuItem) menu.findItem(R.id.action_muc_details);
 		MenuItem menuContactDetails = (MenuItem) menu.findItem(R.id.action_contact_details);
+		MenuItem menuInviteContacts = (MenuItem) menu.findItem(R.id.action_invite);
 		
 		if ((spl.isOpen()&&(spl.isSlideable()))) {
 			menuArchive.setVisible(false);
 			menuMucDetails.setVisible(false);
 			menuContactDetails.setVisible(false);
 			menuSecure.setVisible(false);
+			menuInviteContacts.setVisible(false);
 		} else {
 			((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl.isSlideable());
 			if (this.getSelectedConversation()!=null) {
@@ -263,9 +265,11 @@ public class ConversationActivity extends XmppActivity {
 					menuContactDetails.setVisible(false);
 					menuSecure.setVisible(false);
 					menuArchive.setTitle("Leave conference");
+					menuInviteContacts.setVisible(true);
 				} else {
 					menuContactDetails.setVisible(true);
 					menuMucDetails.setVisible(false);
+					menuInviteContacts.setVisible(false);
 					if (this.getSelectedConversation().getLatestMessage().getEncryption() != Message.ENCRYPTION_NONE) {
 						menuSecure.setIcon(R.drawable.ic_action_secure);
 					}
@@ -282,7 +286,7 @@ public class ConversationActivity extends XmppActivity {
 			spl.openPane();
 			break;
 		case R.id.action_add:
-			startActivity(new Intent(this, NewConversationActivity.class));
+			startActivity(new Intent(this, ContactsActivity.class));
 			break;
 		case R.id.action_archive:
 			Conversation conv = getSelectedConversation();
@@ -319,6 +323,13 @@ public class ConversationActivity extends XmppActivity {
 			intent.putExtra("uuid", getSelectedConversation().getUuid());
 			startActivity(intent);
 			break;
+		case R.id.action_invite:
+			Intent inviteIntent = new Intent(getApplicationContext(),
+					ContactsActivity.class);
+			inviteIntent.setAction("invite");
+			inviteIntent.putExtra("uuid",selectedConversation.getUuid());
+			startActivity(inviteIntent);
+			break;
 		case R.id.action_security:
 			final Conversation selConv = getSelectedConversation();
 			View menuItemView = findViewById(R.id.action_security);
@@ -451,7 +462,7 @@ public class ConversationActivity extends XmppActivity {
 				finish();
 			} else if (conversationList.size() <= 0) {
 				//add no history
-				startActivity(new Intent(this, NewConversationActivity.class));
+				startActivity(new Intent(this, ContactsActivity.class));
 				finish();
 			} else {
 				spl.openPane();

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

@@ -326,7 +326,8 @@ public class ConversationFragment extends Fragment {
 	public void onStart() {
 		super.onStart();
 		ConversationActivity activity = (ConversationActivity) getActivity();
-
+		SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
+		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
 		if (activity.xmppConnectionServiceBound) {
 			this.onBackendConnected();
 		}

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

@@ -200,7 +200,7 @@ public class ManageAccountActivity extends XmppActivity {
 					if ((account.getStatus() == Account.STATUS_OFFLINE)||(account.getStatus() == Account.STATUS_TLS_ERROR)) {
 						activity.xmppConnectionService.reconnectAccount(accountList.get(position),true);
 					} else if (account.getStatus() == Account.STATUS_ONLINE) {
-						activity.startActivity(new Intent(activity.getApplicationContext(),NewConversationActivity.class));
+						activity.startActivity(new Intent(activity.getApplicationContext(),ContactsActivity.class));
 					} else if (account.isOptionSet(Account.OPTION_REGISTER)) {
 						editAccount(account);
 					}

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

@@ -70,7 +70,7 @@ public class MucDetailsActivity extends XmppActivity {
 		@Override
 		public void onClick(View v) {
 			Intent intent = new Intent(getApplicationContext(),
-					NewConversationActivity.class);
+					ContactsActivity.class);
 			intent.setAction("invite");
 			intent.putExtra("uuid",conversation.getUuid());
 			startActivity(intent);

src/eu/siacs/conversations/utils/CryptoHelper.java 🔗

@@ -1,9 +1,14 @@
 package eu.siacs.conversations.utils;
 
+import java.security.SecureRandom;
+import java.util.Random;
+
 import android.util.Base64;
 
 public class CryptoHelper {
 	final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+	final protected static char[] vowels = "aeiou".toCharArray();
+	final protected static char[] consonants ="bcdfghjklmnpqrstvwxyz".toCharArray();
 	public static String bytesToHex(byte[] bytes) {
 	    char[] hexChars = new char[bytes.length * 2];
 	    for ( int j = 0; j < bytes.length; j++ ) {
@@ -31,4 +36,21 @@ public class CryptoHelper {
 		
 		return Base64.encodeToString(saslBytes, Base64.DEFAULT);
 	}
+	
+	public static String randomMucName() {
+		Random random = new SecureRandom();
+		return randomWord(3,random)+"."+randomWord(7,random);
+	}
+	
+	protected static String randomWord(int lenght,Random random) {
+		StringBuilder builder = new StringBuilder(lenght);
+		for(int i = 0; i < lenght; ++i) {
+			if (i % 2 == 0) {
+				builder.append(consonants[random.nextInt(consonants.length)]);
+			} else {
+				builder.append(vowels[random.nextInt(vowels.length)]);
+			}
+		}
+		return builder.toString();
+	}
 }

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

@@ -220,6 +220,7 @@ public class XmppConnection implements Runnable {
 				tagWriter.writeStanzaAsync(r);
 			} else if (nextTag.isStart("resumed")) {
 				tagReader.readElement(nextTag);
+				sendPing();
 				changeStatus(Account.STATUS_ONLINE);
 				Log.d(LOGTAG,account.getJid()+": session resumed");
 			} else if (nextTag.isStart("r")) {
@@ -543,10 +544,6 @@ public class XmppConnection implements Runnable {
 				String resource = packet.findChild("bind").findChild("jid")
 						.getContent().split("/")[1];
 				account.setResource(resource);
-				if (bindListener !=null) {
-					bindListener.onBind(account);
-				}
-				account.setStatus(Account.STATUS_ONLINE);
 				if (streamFeatures.hasChild("sm")) {
 					EnablePacket enable = new EnablePacket();
 					tagWriter.writeStanzaAsync(enable);
@@ -554,9 +551,10 @@ public class XmppConnection implements Runnable {
 				sendInitialPresence();
 				sendServiceDiscoveryInfo();
 				sendServiceDiscoveryItems();
-				if (statusListener != null) {
-					statusListener.onStatusChanged(account);
+				if (bindListener !=null) {
+					bindListener.onBind(account);
 				}
+				account.setStatus(Account.STATUS_ONLINE);
 			}
 		});
 	}