From 03af1d359dbe37eadef5e3ef433f6a0dcc4c2ee3 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Sat, 13 Apr 2013 23:08:51 +0100
Subject: [PATCH] Choose the visibility of blogs and groups when creating them.

---
 briar-android/res/values/strings.xml          |   4 +
 .../briar/android/SelectContactsDialog.java   |  65 +++++++
 .../net/sf/briar/android/SetupActivity.java   |  10 +-
 .../briar/android/blogs/BlogListActivity.java |   6 +-
 .../android/blogs/CreateBlogActivity.java     | 170 +++++++++++++++---
 .../sf/briar/android/blogs/NoBlogsDialog.java |   8 +-
 .../android/groups/CreateGroupActivity.java   | 167 ++++++++++++++---
 .../android/groups/GroupListActivity.java     |   6 +-
 .../briar/android/groups/NoGroupsDialog.java  |   8 +-
 .../identity/CreateIdentityActivity.java      |  10 +-
 .../android/invitation/CodeEntryWidget.java   |  20 ++-
 .../messages/ConversationListActivity.java    |   6 +-
 .../android/messages/NoContactsDialog.java    |  12 +-
 13 files changed, 398 insertions(+), 94 deletions(-)
 create mode 100644 briar-android/src/net/sf/briar/android/SelectContactsDialog.java

diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index d3580ced8b..003bfac943 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -51,10 +51,14 @@
 	<string name="no_posts">(No posts)</string>
 	<string name="create_group_title">New Group</string>
 	<string name="choose_group_name">Choose a name for your group:</string>
+	<string name="group_visible_to_all">Share this group with all contacts</string>
+	<string name="group_visible_to_some">Share this group with chosen contacts</string>
 	<string name="compose_group_title">New Post</string>
 	<string name="blogs_title">Blogs</string>
 	<string name="create_blog_title">New Blog</string>
 	<string name="choose_blog_name">Choose a name for your blog:</string>
+	<string name="blog_visible_to_all">Share this blog with all contacts</string>
+	<string name="blog_visible_to_some">Share this blog with chosen contacts</string>
 	<string name="compose_blog_title">New Post</string>
 	<string name="create_nickname_item">New nickname\u2026</string>
 	<string name="create_identity_title">Create an Identity</string>
diff --git a/briar-android/src/net/sf/briar/android/SelectContactsDialog.java b/briar-android/src/net/sf/briar/android/SelectContactsDialog.java
new file mode 100644
index 0000000000..fa74ee1aa7
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/SelectContactsDialog.java
@@ -0,0 +1,65 @@
+package net.sf.briar.android;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import net.sf.briar.R;
+import net.sf.briar.api.Contact;
+import net.sf.briar.api.ContactId;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+public class SelectContactsDialog extends DialogFragment
+implements DialogInterface.OnMultiChoiceClickListener {
+
+	private final Set<ContactId> selected = new HashSet<ContactId>();
+
+	private Listener listener = null;
+	private Contact[] contacts = null;
+
+	public void setListener(Listener listener) {
+		this.listener = listener;
+	}
+
+	public void setContacts(Collection<Contact> contacts) {
+		this.contacts = contacts.toArray(this.contacts);
+	}
+
+	@Override
+	public Dialog onCreateDialog(Bundle state) {
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		String[] names = new String[contacts.length];
+		for(int i = 0; i < contacts.length; i++)
+			names[i] = contacts[i].getAuthor().getName();
+		builder.setMultiChoiceItems(names, new boolean[contacts.length], this);
+		builder.setPositiveButton(R.string.done_button,
+				new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int id) {
+				listener.contactsSelected(selected);
+			}
+		});
+		builder.setNegativeButton(R.string.cancel_button,
+				new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int id) {
+				listener.contactSelectionCancelled();
+			}
+		});
+		return builder.create();
+	}
+
+	public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+		if(isChecked) selected.add(contacts[which].getId());
+		else selected.remove(contacts[which].getId());
+	}
+
+	public interface Listener {
+
+		void contactsSelected(Collection<ContactId> selected);
+
+		void contactSelectionCancelled();
+	}
+}
diff --git a/briar-android/src/net/sf/briar/android/SetupActivity.java b/briar-android/src/net/sf/briar/android/SetupActivity.java
index 0ece0cad49..a612cc7b28 100644
--- a/briar-android/src/net/sf/briar/android/SetupActivity.java
+++ b/briar-android/src/net/sf/briar/android/SetupActivity.java
@@ -65,7 +65,14 @@ implements OnEditorActionListener, OnClickListener {
 		chooseNickname.setText(R.string.choose_nickname);
 		layout.addView(chooseNickname);
 
-		nicknameEntry = new EditText(this);
+		nicknameEntry = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				if(createButton != null)
+					createButton.setEnabled(lengthAfter > 0);
+			}
+		};
 		nicknameEntry.setTextSize(18);
 		nicknameEntry.setMaxLines(1);
 		nicknameEntry.setPadding(10, 10, 10, 10);
@@ -77,6 +84,7 @@ implements OnEditorActionListener, OnClickListener {
 		createButton = new Button(this);
 		createButton.setLayoutParams(WRAP_WRAP);
 		createButton.setText(R.string.create_button);
+		createButton.setEnabled(false);
 		createButton.setOnClickListener(this);
 		layout.addView(createButton);
 
diff --git a/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java b/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java
index b6dfdfdcab..134c4f2fab 100644
--- a/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/BlogListActivity.java
@@ -289,13 +289,11 @@ implements OnClickListener, DatabaseListener, NoBlogsDialog.Listener {
 		});
 	}
 
-	public void createGroupButtonClicked() {
+	public void blogCreationSelected() {
 		startActivity(new Intent(this, CreateBlogActivity.class));
 	}
 
-	public void cancelButtonClicked() {
-		// That's nice dear
-	}
+	public void blogCreationCancelled() {}
 
 	private static class GroupComparator implements Comparator<BlogListItem> {
 
diff --git a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
index 6d9ce55b7b..84ec78b6ce 100644
--- a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
@@ -15,13 +15,20 @@ import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP;
 
 import java.io.IOException;
 import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarActivity;
+import net.sf.briar.android.BriarFragmentActivity;
 import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.android.SelectContactsDialog;
+import net.sf.briar.android.invitation.AddContactActivity;
+import net.sf.briar.android.messages.NoContactsDialog;
+import net.sf.briar.api.Contact;
+import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.CryptoExecutor;
@@ -39,13 +46,16 @@ import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
 import com.google.inject.Inject;
 
-public class CreateBlogActivity extends BriarActivity
-implements OnEditorActionListener, OnClickListener {
+public class CreateBlogActivity extends BriarFragmentActivity
+implements OnEditorActionListener, OnClickListener, NoContactsDialog.Listener,
+SelectContactsDialog.Listener {
 
 	private static final Logger LOG =
 			Logger.getLogger(CreateBlogActivity.class.getName());
@@ -55,6 +65,8 @@ implements OnEditorActionListener, OnClickListener {
 
 	@Inject @CryptoExecutor private Executor cryptoExecutor;
 	private EditText nameEntry = null;
+	private RadioGroup radioGroup = null;
+	private RadioButton visibleToAll = null, visibleToSome = null;
 	private Button createButton = null;
 	private ProgressBar progress = null;
 
@@ -63,6 +75,7 @@ implements OnEditorActionListener, OnClickListener {
 	@Inject private volatile GroupFactory groupFactory;
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	private volatile Collection<ContactId> selected = Collections.emptyList();
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -72,25 +85,45 @@ implements OnEditorActionListener, OnClickListener {
 		layout.setOrientation(VERTICAL);
 		layout.setGravity(CENTER_HORIZONTAL);
 
-		TextView chooseNickname = new TextView(this);
-		chooseNickname.setGravity(CENTER);
-		chooseNickname.setTextSize(18);
-		chooseNickname.setPadding(10, 10, 10, 10);
-		chooseNickname.setText(R.string.choose_blog_name);
-		layout.addView(chooseNickname);
+		TextView chooseName = new TextView(this);
+		chooseName.setGravity(CENTER);
+		chooseName.setTextSize(18);
+		chooseName.setPadding(10, 10, 10, 10);
+		chooseName.setText(R.string.choose_blog_name);
+		layout.addView(chooseName);
 
-		nameEntry = new EditText(this);
+		nameEntry = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				enableOrDisableCreateButton();
+			}
+		};
 		nameEntry.setTextSize(18);
 		nameEntry.setMaxLines(1);
 		nameEntry.setPadding(10, 10, 10, 10);
-		int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES;
-		nameEntry.setInputType(inputType);
+		nameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
 		nameEntry.setOnEditorActionListener(this);
 		layout.addView(nameEntry);
 
+		radioGroup = new RadioGroup(this);
+		radioGroup.setOrientation(VERTICAL);
+
+		visibleToAll = new RadioButton(this);
+		visibleToAll.setText(R.string.blog_visible_to_all);
+		visibleToAll.setOnClickListener(this);
+		radioGroup.addView(visibleToAll);
+
+		visibleToSome = new RadioButton(this);
+		visibleToSome.setText(R.string.blog_visible_to_some);
+		visibleToSome.setOnClickListener(this);
+		radioGroup.addView(visibleToSome);
+		layout.addView(radioGroup);
+
 		createButton = new Button(this);
 		createButton.setLayoutParams(WRAP_WRAP);
 		createButton.setText(R.string.create_button);
+		createButton.setEnabled(false);
 		createButton.setOnClickListener(this);
 		layout.addView(createButton);
 
@@ -107,6 +140,14 @@ implements OnEditorActionListener, OnClickListener {
 				serviceConnection, 0);
 	}
 
+	private void enableOrDisableCreateButton() {
+		if(nameEntry == null || radioGroup == null || createButton == null)
+			return; // Activity not created yet
+		boolean nameNotEmpty = nameEntry.getText().length() > 0;
+		boolean visibilitySelected = radioGroup.getCheckedRadioButtonId() != -1;
+		createButton.setEnabled(nameNotEmpty && visibilitySelected);
+	}
+
 	@Override
 	public void onDestroy() {
 		super.onDestroy();
@@ -119,30 +160,82 @@ implements OnEditorActionListener, OnClickListener {
 	}
 
 	public void onClick(View view) {
-		if(!validateName()) return;
-		final String name = nameEntry.getText().toString();
-		// Replace the button with a progress bar
-		createButton.setVisibility(GONE);
-		progress.setVisibility(VISIBLE);
-		// Create the blog in a background thread
-		cryptoExecutor.execute(new Runnable() {
+		if(view == visibleToAll) {
+			enableOrDisableCreateButton();
+		} else if(view == visibleToSome) {
+			loadContacts();
+		} else if(view == createButton) {
+			if(!validateName()) return;
+			final String name = nameEntry.getText().toString();
+			final boolean all = visibleToAll.isChecked();
+			final Collection<ContactId> visible =
+					Collections.unmodifiableCollection(selected);
+			// Replace the button with a progress bar
+			createButton.setVisibility(GONE);
+			progress.setVisibility(VISIBLE);
+			// Create the blog in a background thread
+			cryptoExecutor.execute(new Runnable() {
+				public void run() {
+					KeyPair keyPair = crypto.generateSignatureKeyPair();
+					final byte[] publicKey = keyPair.getPublic().getEncoded();
+					final byte[] privateKey = keyPair.getPrivate().getEncoded();
+					LocalGroup g;
+					try {
+						g = groupFactory.createLocalGroup(name, publicKey,
+								privateKey);
+					} catch(IOException e) {
+						throw new RuntimeException(e);
+					}
+					storeLocalGroup(g, all, visible);
+				}
+			});
+		}
+	}
+
+	private void loadContacts() {
+		dbUiExecutor.execute(new Runnable() {
 			public void run() {
-				KeyPair keyPair = crypto.generateSignatureKeyPair();
-				final byte[] publicKey = keyPair.getPublic().getEncoded();
-				final byte[] privateKey = keyPair.getPrivate().getEncoded();
-				LocalGroup g;
 				try {
-					g = groupFactory.createLocalGroup(name, publicKey,
-							privateKey);
-				} catch(IOException e) {
-					throw new RuntimeException(e);
+					serviceConnection.waitForStartup();
+					long now = System.currentTimeMillis();
+					Collection<Contact> contacts = db.getContacts();
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Load took " + duration + " ms");
+					displayContacts(contacts);
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(InterruptedException e) {
+					if(LOG.isLoggable(INFO))
+						LOG.info("Interrupted while waiting for service");
+					Thread.currentThread().interrupt();
+				}
+			}
+		});
+	}
+
+	private void displayContacts(final Collection<Contact> contacts) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				if(contacts.isEmpty()) {
+					NoContactsDialog dialog = new NoContactsDialog();
+					dialog.setListener(CreateBlogActivity.this);
+					dialog.show(getSupportFragmentManager(),
+							"NoContactsDialog");
+				} else {
+					SelectContactsDialog dialog = new SelectContactsDialog();
+					dialog.setListener(CreateBlogActivity.this);
+					dialog.setContacts(contacts);
+					dialog.show(getSupportFragmentManager(),
+							"SelectContactsDialog");
 				}
-				storeLocalGroup(g);
 			}
 		});
 	}
 
-	private void storeLocalGroup(final LocalGroup g) {
+	private void storeLocalGroup(final LocalGroup g, final boolean all,
+			final Collection<ContactId> visible) {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
@@ -150,6 +243,8 @@ implements OnEditorActionListener, OnClickListener {
 					long now = System.currentTimeMillis();
 					db.addLocalGroup(g);
 					db.subscribe(g);
+					if(all) db.setVisibleToAll(g.getId(), true);
+					else db.setVisibility(g.getId(), visible);
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Storing group took " + duration + " ms");
@@ -177,4 +272,21 @@ implements OnEditorActionListener, OnClickListener {
 		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
 		return true;
 	}
+
+	public void contactCreationSelected() {
+		startActivity(new Intent(this, AddContactActivity.class));
+	}
+
+	public void contactCreationCancelled() {
+		enableOrDisableCreateButton();
+	}
+
+	public void contactsSelected(Collection<ContactId> selected) {
+		this.selected = selected;
+		enableOrDisableCreateButton();
+	}
+
+	public void contactSelectionCancelled() {
+		enableOrDisableCreateButton();
+	}
 }
diff --git a/briar-android/src/net/sf/briar/android/blogs/NoBlogsDialog.java b/briar-android/src/net/sf/briar/android/blogs/NoBlogsDialog.java
index 15b15cfbea..b835960ae9 100644
--- a/briar-android/src/net/sf/briar/android/blogs/NoBlogsDialog.java
+++ b/briar-android/src/net/sf/briar/android/blogs/NoBlogsDialog.java
@@ -22,13 +22,13 @@ public class NoBlogsDialog extends DialogFragment {
 		builder.setPositiveButton(R.string.create_button,
 				new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int id) {
-				listener.createGroupButtonClicked();
+				listener.blogCreationSelected();
 			}
 		});
 		builder.setNegativeButton(R.string.cancel_button,
 				new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int id) {
-				listener.cancelButtonClicked();
+				listener.blogCreationCancelled();
 			}
 		});
 		return builder.create();
@@ -36,8 +36,8 @@ public class NoBlogsDialog extends DialogFragment {
 
 	interface Listener {
 
-		void createGroupButtonClicked();
+		void blogCreationSelected();
 
-		void cancelButtonClicked();
+		void blogCreationCancelled();
 	}
 }
diff --git a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
index e8c3ac199e..57b280767b 100644
--- a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
@@ -14,13 +14,20 @@ import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
 import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP;
 
 import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 import net.sf.briar.R;
-import net.sf.briar.android.BriarActivity;
+import net.sf.briar.android.BriarFragmentActivity;
 import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.android.SelectContactsDialog;
+import net.sf.briar.android.invitation.AddContactActivity;
+import net.sf.briar.android.messages.NoContactsDialog;
+import net.sf.briar.api.Contact;
+import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
@@ -36,13 +43,16 @@ import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
 import com.google.inject.Inject;
 
-public class CreateGroupActivity extends BriarActivity
-implements OnEditorActionListener, OnClickListener {
+public class CreateGroupActivity extends BriarFragmentActivity
+implements OnEditorActionListener, OnClickListener, NoContactsDialog.Listener,
+SelectContactsDialog.Listener {
 
 	private static final Logger LOG =
 			Logger.getLogger(CreateGroupActivity.class.getName());
@@ -51,6 +61,8 @@ implements OnEditorActionListener, OnClickListener {
 			new BriarServiceConnection();
 
 	private EditText nameEntry = null;
+	private RadioGroup radioGroup = null;
+	private RadioButton visibleToAll = null, visibleToSome = null;
 	private Button createButton = null;
 	private ProgressBar progress = null;
 
@@ -58,6 +70,7 @@ implements OnEditorActionListener, OnClickListener {
 	@Inject private volatile GroupFactory groupFactory;
 	@Inject private volatile DatabaseComponent db;
 	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	private volatile Collection<ContactId> selected = Collections.emptyList();
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -67,22 +80,41 @@ implements OnEditorActionListener, OnClickListener {
 		layout.setOrientation(VERTICAL);
 		layout.setGravity(CENTER_HORIZONTAL);
 
-		TextView chooseNickname = new TextView(this);
-		chooseNickname.setGravity(CENTER);
-		chooseNickname.setTextSize(18);
-		chooseNickname.setPadding(10, 10, 10, 10);
-		chooseNickname.setText(R.string.choose_group_name);
-		layout.addView(chooseNickname);
+		TextView chooseName = new TextView(this);
+		chooseName.setGravity(CENTER);
+		chooseName.setTextSize(18);
+		chooseName.setPadding(10, 10, 10, 10);
+		chooseName.setText(R.string.choose_group_name);
+		layout.addView(chooseName);
 
-		nameEntry = new EditText(this);
+		nameEntry = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				enableOrDisableCreateButton();
+			}
+		};
 		nameEntry.setTextSize(18);
 		nameEntry.setMaxLines(1);
 		nameEntry.setPadding(10, 10, 10, 10);
-		int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES;
-		nameEntry.setInputType(inputType);
+		nameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
 		nameEntry.setOnEditorActionListener(this);
 		layout.addView(nameEntry);
 
+		radioGroup = new RadioGroup(this);
+		radioGroup.setOrientation(VERTICAL);
+
+		visibleToAll = new RadioButton(this);
+		visibleToAll.setText(R.string.blog_visible_to_all);
+		visibleToAll.setOnClickListener(this);
+		radioGroup.addView(visibleToAll);
+
+		visibleToSome = new RadioButton(this);
+		visibleToSome.setText(R.string.blog_visible_to_some);
+		visibleToSome.setOnClickListener(this);
+		radioGroup.addView(visibleToSome);
+		layout.addView(radioGroup);
+
 		createButton = new Button(this);
 		createButton.setLayoutParams(WRAP_WRAP);
 		createButton.setText(R.string.create_button);
@@ -102,6 +134,14 @@ implements OnEditorActionListener, OnClickListener {
 				serviceConnection, 0);
 	}
 
+	private void enableOrDisableCreateButton() {
+		if(nameEntry == null || radioGroup == null || createButton == null)
+			return; // Activity not created yet
+		boolean nameNotEmpty = nameEntry.getText().length() > 0;
+		boolean visibilitySelected = radioGroup.getCheckedRadioButtonId() != -1;
+		createButton.setEnabled(nameNotEmpty && visibilitySelected);
+	}
+
 	@Override
 	public void onDestroy() {
 		super.onDestroy();
@@ -114,22 +154,64 @@ implements OnEditorActionListener, OnClickListener {
 	}
 
 	public void onClick(View view) {
-		if(!validateName()) return;
-		final String name = nameEntry.getText().toString();
-		// Replace the button with a progress bar
-		createButton.setVisibility(GONE);
-		progress.setVisibility(VISIBLE);
-		// Create and store the group in a background thread
+		if(view == visibleToAll) {
+			enableOrDisableCreateButton();
+		} else if(view == visibleToSome) {
+			loadContacts();
+		} else if(view == createButton) {
+			if(!validateName()) return;
+			final String name = nameEntry.getText().toString();
+			final boolean all = visibleToAll.isChecked();
+			final Collection<ContactId> visible =
+					Collections.unmodifiableCollection(selected);
+			// Replace the button with a progress bar
+			createButton.setVisibility(GONE);
+			progress.setVisibility(VISIBLE);
+			// Create and store the group in a background thread
+			dbUiExecutor.execute(new Runnable() {
+				public void run() {
+					try {
+						serviceConnection.waitForStartup();
+						Group g = groupFactory.createGroup(name);
+						long now = System.currentTimeMillis();
+						db.subscribe(g);
+						if(all) db.setVisibleToAll(g.getId(), true);
+						else db.setVisibility(g.getId(), visible);
+						long duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO))
+							LOG.info("Storing group took " + duration + " ms");
+					} catch(DbException e) {
+						if(LOG.isLoggable(WARNING))
+							LOG.log(WARNING, e.toString(), e);
+					} catch(InterruptedException e) {
+						if(LOG.isLoggable(INFO))
+							LOG.info("Interrupted while waiting for service");
+						Thread.currentThread().interrupt();
+					} catch(IOException e) {
+						throw new RuntimeException(e);
+					}
+					runOnUiThread(new Runnable() {
+						public void run() {
+							finish();
+						}
+					});
+				}
+			});
+		}
+	}
+
+
+	private void loadContacts() {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
-					Group g = groupFactory.createGroup(name);
 					long now = System.currentTimeMillis();
-					db.subscribe(g);
+					Collection<Contact> contacts = db.getContacts();
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
-						LOG.info("Storing group took " + duration + " ms");
+						LOG.info("Load took " + duration + " ms");
+					displayContacts(contacts);
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -137,14 +219,26 @@ implements OnEditorActionListener, OnClickListener {
 					if(LOG.isLoggable(INFO))
 						LOG.info("Interrupted while waiting for service");
 					Thread.currentThread().interrupt();
-				} catch(IOException e) {
-					throw new RuntimeException(e);
 				}
-				runOnUiThread(new Runnable() {
-					public void run() {
-						finish();
-					}
-				});
+			}
+		});
+	}
+
+	private void displayContacts(final Collection<Contact> contacts) {
+		runOnUiThread(new Runnable() {
+			public void run() {
+				if(contacts.isEmpty()) {
+					NoContactsDialog dialog = new NoContactsDialog();
+					dialog.setListener(CreateGroupActivity.this);
+					dialog.show(getSupportFragmentManager(),
+							"NoContactsDialog");
+				} else {
+					SelectContactsDialog dialog = new SelectContactsDialog();
+					dialog.setListener(CreateGroupActivity.this);
+					dialog.setContacts(contacts);
+					dialog.show(getSupportFragmentManager(),
+							"SelectContactsDialog");
+				}
 			}
 		});
 	}
@@ -156,4 +250,21 @@ implements OnEditorActionListener, OnClickListener {
 		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
 		return true;
 	}
+
+	public void contactCreationSelected() {
+		startActivity(new Intent(this, AddContactActivity.class));
+	}
+
+	public void contactCreationCancelled() {
+		enableOrDisableCreateButton();
+	}
+
+	public void contactsSelected(Collection<ContactId> selected) {
+		this.selected = selected;
+		enableOrDisableCreateButton();
+	}
+
+	public void contactSelectionCancelled() {
+		enableOrDisableCreateButton();
+	}
 }
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index 083497f91f..570198436c 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -276,13 +276,11 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener {
 		});
 	}
 
-	public void createButtonClicked() {
+	public void groupCreationSelected() {
 		startActivity(new Intent(this, CreateGroupActivity.class));
 	}
 
-	public void cancelButtonClicked() {
-		// That's nice dear
-	}
+	public void groupCreationCancelled() {}
 
 	private static class GroupComparator implements Comparator<GroupListItem> {
 
diff --git a/briar-android/src/net/sf/briar/android/groups/NoGroupsDialog.java b/briar-android/src/net/sf/briar/android/groups/NoGroupsDialog.java
index 34613b52c9..1c44a75121 100644
--- a/briar-android/src/net/sf/briar/android/groups/NoGroupsDialog.java
+++ b/briar-android/src/net/sf/briar/android/groups/NoGroupsDialog.java
@@ -22,13 +22,13 @@ public class NoGroupsDialog extends DialogFragment {
 		builder.setPositiveButton(R.string.create_button,
 				new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int id) {
-				listener.createButtonClicked();
+				listener.groupCreationSelected();
 			}
 		});
 		builder.setNegativeButton(R.string.cancel_button,
 				new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int id) {
-				listener.cancelButtonClicked();
+				listener.groupCreationCancelled();
 			}
 		});
 		return builder.create();
@@ -36,8 +36,8 @@ public class NoGroupsDialog extends DialogFragment {
 
 	interface Listener {
 
-		void createButtonClicked();
+		void groupCreationSelected();
 
-		void cancelButtonClicked();
+		void groupCreationCancelled();
 	}
 }
diff --git a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
index 5a3fd55650..a600f95489 100644
--- a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
+++ b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
@@ -79,7 +79,14 @@ implements OnEditorActionListener, OnClickListener {
 		chooseNickname.setText(R.string.choose_nickname);
 		layout.addView(chooseNickname);
 
-		nicknameEntry = new EditText(this);
+		nicknameEntry = new EditText(this) {
+			@Override
+			protected void onTextChanged(CharSequence text, int start,
+					int lengthBefore, int lengthAfter) {
+				if(createButton != null)
+					createButton.setEnabled(lengthAfter > 0);
+			}
+		};
 		nicknameEntry.setTextSize(18);
 		nicknameEntry.setMaxLines(1);
 		nicknameEntry.setPadding(10, 10, 10, 10);
@@ -91,6 +98,7 @@ implements OnEditorActionListener, OnClickListener {
 		createButton = new Button(this);
 		createButton.setLayoutParams(WRAP_WRAP);
 		createButton.setText(R.string.create_button);
+		createButton.setEnabled(false);
 		createButton.setOnClickListener(this);
 		layout.addView(createButton);
 
diff --git a/briar-android/src/net/sf/briar/android/invitation/CodeEntryWidget.java b/briar-android/src/net/sf/briar/android/invitation/CodeEntryWidget.java
index 1cc1f16e2b..da6576e727 100644
--- a/briar-android/src/net/sf/briar/android/invitation/CodeEntryWidget.java
+++ b/briar-android/src/net/sf/briar/android/invitation/CodeEntryWidget.java
@@ -23,6 +23,7 @@ implements OnEditorActionListener, OnClickListener {
 
 	private CodeEntryListener listener = null;
 	private EditText codeEntry = null;
+	private Button continueButton = null;
 
 	public CodeEntryWidget(Context ctx) {
 		super(ctx);
@@ -45,17 +46,12 @@ implements OnEditorActionListener, OnClickListener {
 		innerLayout.setOrientation(HORIZONTAL);
 		innerLayout.setGravity(CENTER);
 
-		final Button continueButton = new Button(ctx);
-		continueButton.setLayoutParams(WRAP_WRAP);
-		continueButton.setText(R.string.continue_button);
-		continueButton.setEnabled(false);
-		continueButton.setOnClickListener(this);
-
 		codeEntry = new EditText(ctx) {
 			@Override
 			protected void onTextChanged(CharSequence text, int start,
 					int lengthBefore, int lengthAfter) {
-				continueButton.setEnabled(text.length() == 6);
+				if(continueButton != null)
+					continueButton.setEnabled(lengthAfter == 6);
 			}
 		};
 		codeEntry.setTextSize(26);
@@ -66,6 +62,12 @@ implements OnEditorActionListener, OnClickListener {
 		codeEntry.setMaxLines(1);
 		codeEntry.setInputType(TYPE_CLASS_NUMBER);
 		innerLayout.addView(codeEntry);
+
+		continueButton = new Button(ctx);
+		continueButton.setLayoutParams(WRAP_WRAP);
+		continueButton.setText(R.string.continue_button);
+		continueButton.setEnabled(false);
+		continueButton.setOnClickListener(this);
 		innerLayout.addView(continueButton);
 		addView(innerLayout);
 	}
@@ -89,8 +91,8 @@ implements OnEditorActionListener, OnClickListener {
 		}
 		// Hide the soft keyboard
 		Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
-	    ((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
-	    listener.codeEntered(remoteCode);
+		((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
+		listener.codeEntered(remoteCode);
 		return true;
 	}
 }
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
index 746552eee1..16db46e339 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -238,13 +238,11 @@ implements OnClickListener, DatabaseListener, NoContactsDialog.Listener {
 		});
 	}
 
-	public void addContactButtonClicked() {
+	public void contactCreationSelected() {
 		startActivity(new Intent(this, AddContactActivity.class));
 	}
 
-	public void cancelButtonClicked() {
-		// That's nice dear
-	}
+	public void contactCreationCancelled() {}
 
 	private static class ConversationComparator
 	implements Comparator<ConversationListItem> {
diff --git a/briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java b/briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java
index 21162f2a66..ddef88e455 100644
--- a/briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java
+++ b/briar-android/src/net/sf/briar/android/messages/NoContactsDialog.java
@@ -11,7 +11,7 @@ public class NoContactsDialog extends DialogFragment {
 
 	private Listener listener = null;
 
-	void setListener(Listener listener) {
+	public void setListener(Listener listener) {
 		this.listener = listener;
 	}
 
@@ -22,22 +22,22 @@ public class NoContactsDialog extends DialogFragment {
 		builder.setPositiveButton(R.string.add_button,
 				new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int id) {
-				listener.addContactButtonClicked();
+				listener.contactCreationSelected();
 			}
 		});
 		builder.setNegativeButton(R.string.cancel_button,
 				new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int id) {
-				listener.cancelButtonClicked();
+				listener.contactCreationCancelled();
 			}
 		});
 		return builder.create();
 	}
 
-	interface Listener {
+	public interface Listener {
 
-		void addContactButtonClicked();
+		void contactCreationSelected();
 
-		void cancelButtonClicked();
+		void contactCreationCancelled();
 	}
 }
-- 
GitLab