From f1ebbc28f3f8e5f030acbf37a397ce155dcd707c Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Fri, 12 Apr 2013 20:17:34 +0100 Subject: [PATCH] Separated WriteGroupMessageActivity into group and blog activities. --- briar-android/AndroidManifest.xml | 8 +- .../briar/android/groups/GroupActivity.java | 13 +- .../android/groups/GroupListActivity.java | 6 +- .../groups/LocalGroupSpinnerAdapter.java | 40 +++ .../groups/ReadGroupMessageActivity.java | 3 +- .../android/groups/WriteBlogPostActivity.java | 333 ++++++++++++++++++ ...ivity.java => WriteGroupPostActivity.java} | 56 +-- 7 files changed, 422 insertions(+), 37 deletions(-) create mode 100644 briar-android/src/net/sf/briar/android/groups/LocalGroupSpinnerAdapter.java create mode 100644 briar-android/src/net/sf/briar/android/groups/WriteBlogPostActivity.java rename briar-android/src/net/sf/briar/android/groups/{WriteGroupMessageActivity.java => WriteGroupPostActivity.java} (88%) diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index a034123a8c..fc74fb36e3 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -65,8 +65,12 @@ android:label="@string/app_name" > </activity> <activity - android:name=".android.groups.WriteGroupMessageActivity" - android:label="@string/app_name" > + android:name=".android.groups.WriteBlogPostActivity" + android:label="@string/compose_blog_title" > + </activity> + <activity + android:name=".android.groups.WriteGroupPostActivity" + android:label="@string/compose_group_title" > </activity> <activity android:name=".android.identity.CreateIdentityActivity" diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java index 289c85c592..b72e3d2f50 100644 --- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java @@ -208,10 +208,15 @@ OnClickListener, OnItemClickListener { } public void onClick(View view) { - Intent i = new Intent(this, WriteGroupMessageActivity.class); - i.putExtra("net.sf.briar.RESTRICTED", restricted); - i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); - startActivity(i); + if(restricted) { + Intent i = new Intent(this, WriteBlogPostActivity.class); + i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); + startActivity(i); + } else { + Intent i = new Intent(this, WriteGroupPostActivity.class); + i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); + startActivity(i); + } } public void onItemClick(AdapterView<?> parent, View view, int position, 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 8f58340846..5ca3267d5b 100644 --- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java @@ -224,10 +224,10 @@ implements OnClickListener, DatabaseListener, NoGroupsDialog.Listener { dialog.setListener(this); dialog.setRestricted(restricted); dialog.show(getSupportFragmentManager(), "NoGroupsDialog"); + } else if(restricted) { + startActivity(new Intent(this, WriteBlogPostActivity.class)); } else { - Intent i = new Intent(this, WriteGroupMessageActivity.class); - i.putExtra("net.sf.briar.RESTRICTED", restricted); - startActivity(i); + startActivity(new Intent(this, WriteGroupPostActivity.class)); } } } diff --git a/briar-android/src/net/sf/briar/android/groups/LocalGroupSpinnerAdapter.java b/briar-android/src/net/sf/briar/android/groups/LocalGroupSpinnerAdapter.java new file mode 100644 index 0000000000..34a2f3ffa9 --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/LocalGroupSpinnerAdapter.java @@ -0,0 +1,40 @@ +package net.sf.briar.android.groups; + +import java.util.ArrayList; + +import net.sf.briar.R; +import net.sf.briar.api.messaging.LocalGroup; +import android.content.Context; +import android.content.res.Resources; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +class LocalGroupSpinnerAdapter extends ArrayAdapter<LocalGroup> +implements SpinnerAdapter { + + LocalGroupSpinnerAdapter(Context context) { + super(context, android.R.layout.simple_spinner_item, + new ArrayList<LocalGroup>()); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView name = new TextView(getContext()); + name.setTextSize(18); + name.setMaxLines(1); + Resources res = getContext().getResources(); + int pad = res.getInteger(R.integer.spinner_padding); + name.setPadding(pad, pad, pad, pad); + name.setText(getItem(position).getName()); + return name; + } + + @Override + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + return getView(position, convertView, parent); + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java index 001f1ccc3e..ab2299af0a 100644 --- a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java @@ -325,7 +325,8 @@ implements OnClickListener { setResult(RESULT_NEXT); finish(); } else if(view == replyButton) { - Intent i = new Intent(this, WriteGroupMessageActivity.class); + // FIXME: Restricted/unrestricted + Intent i = new Intent(this, WriteGroupPostActivity.class); i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes()); startActivity(i); diff --git a/briar-android/src/net/sf/briar/android/groups/WriteBlogPostActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteBlogPostActivity.java new file mode 100644 index 0000000000..f620244855 --- /dev/null +++ b/briar-android/src/net/sf/briar/android/groups/WriteBlogPostActivity.java @@ -0,0 +1,333 @@ +package net.sf.briar.android.groups; + +import static android.text.InputType.TYPE_CLASS_TEXT; +import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; +import static android.view.Gravity.CENTER_VERTICAL; +import static android.widget.LinearLayout.HORIZONTAL; +import static android.widget.LinearLayout.VERTICAL; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static net.sf.briar.android.identity.LocalAuthorItem.ANONYMOUS; +import static net.sf.briar.android.identity.LocalAuthorItem.NEW; +import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_WRAP; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.util.Collection; +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.BriarService; +import net.sf.briar.android.BriarService.BriarServiceConnection; +import net.sf.briar.android.identity.CreateIdentityActivity; +import net.sf.briar.android.identity.LocalAuthorItem; +import net.sf.briar.android.identity.LocalAuthorItemComparator; +import net.sf.briar.android.identity.LocalAuthorSpinnerAdapter; +import net.sf.briar.android.widgets.HorizontalSpace; +import net.sf.briar.api.LocalAuthor; +import net.sf.briar.api.android.BundleEncrypter; +import net.sf.briar.api.android.DatabaseUiExecutor; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.crypto.KeyParser; +import net.sf.briar.api.db.DatabaseComponent; +import net.sf.briar.api.db.DbException; +import net.sf.briar.api.messaging.GroupId; +import net.sf.briar.api.messaging.LocalGroup; +import net.sf.briar.api.messaging.Message; +import net.sf.briar.api.messaging.MessageFactory; +import net.sf.briar.api.messaging.MessageId; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import com.google.inject.Inject; + +public class WriteBlogPostActivity extends BriarActivity +implements OnItemSelectedListener, OnClickListener { + + private static final Logger LOG = + Logger.getLogger(WriteBlogPostActivity.class.getName()); + + private final BriarServiceConnection serviceConnection = + new BriarServiceConnection(); + + @Inject private BundleEncrypter bundleEncrypter; + @Inject private CryptoComponent crypto; + @Inject private MessageFactory messageFactory; + private LocalAuthorSpinnerAdapter fromAdapter = null; + private LocalGroupSpinnerAdapter toAdapter = null; + private Spinner fromSpinner = null, toSpinner = null; + private ImageButton sendButton = null; + private EditText content = null; + + // Fields that are accessed from background threads must be volatile + @Inject private volatile DatabaseComponent db; + @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor; + private volatile LocalAuthor localAuthor = null; + private volatile LocalGroup localGroup = null; + private volatile GroupId localGroupId = null; + private volatile MessageId parentId = null; + + @Override + public void onCreate(Bundle state) { + super.onCreate(null); + + Intent i = getIntent(); + byte[] b = i.getByteArrayExtra("net.sf.briar.GROUP_ID"); + if(b != null) localGroupId = new GroupId(b); + b = i.getByteArrayExtra("net.sf.briar.PARENT_ID"); + if(b != null) parentId = new MessageId(b); + + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(MATCH_WRAP); + layout.setOrientation(VERTICAL); + + LinearLayout header = new LinearLayout(this); + header.setLayoutParams(MATCH_WRAP); + header.setOrientation(HORIZONTAL); + header.setGravity(CENTER_VERTICAL); + + TextView from = new TextView(this); + from.setTextSize(18); + from.setPadding(10, 10, 10, 10); + from.setText(R.string.from); + header.addView(from); + + fromAdapter = new LocalAuthorSpinnerAdapter(this, true); + fromSpinner = new Spinner(this); + fromSpinner.setAdapter(fromAdapter); + fromSpinner.setOnItemSelectedListener(this); + header.addView(fromSpinner); + + header.addView(new HorizontalSpace(this)); + + sendButton = new ImageButton(this); + sendButton.setBackgroundResource(0); + sendButton.setImageResource(R.drawable.social_send_now); + sendButton.setEnabled(false); // Enabled when a group is selected + sendButton.setOnClickListener(this); + header.addView(sendButton); + layout.addView(header); + + header = new LinearLayout(this); + header.setLayoutParams(MATCH_WRAP); + header.setOrientation(HORIZONTAL); + header.setGravity(CENTER_VERTICAL); + + TextView to = new TextView(this); + to.setTextSize(18); + to.setPadding(10, 0, 0, 10); + to.setText(R.string.to); + header.addView(to); + + toAdapter = new LocalGroupSpinnerAdapter(this); + toSpinner = new Spinner(this); + toSpinner.setAdapter(toAdapter); + toSpinner.setOnItemSelectedListener(this); + header.addView(toSpinner); + layout.addView(header); + + content = new EditText(this); + content.setPadding(10, 10, 10, 10); + int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES; + content.setInputType(inputType); + if(state != null && bundleEncrypter.decrypt(state)) { + Parcelable p = state.getParcelable("net.sf.briar.CONTENT"); + if(p != null) content.onRestoreInstanceState(p); + } + layout.addView(content); + + setContentView(layout); + + // Bind to the service so we can wait for it to start + bindService(new Intent(BriarService.class.getName()), + serviceConnection, 0); + } + + @Override + public void onResume() { + super.onResume(); + loadLocalAuthors(); + loadLocalGroups(); + } + + private void loadLocalAuthors() { + dbUiExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + long now = System.currentTimeMillis(); + Collection<LocalAuthor> localAuthors = db.getLocalAuthors(); + long duration = System.currentTimeMillis() - now; + if(LOG.isLoggable(INFO)) + LOG.info("Loading authors took " + duration + " ms"); + displayLocalAuthors(localAuthors); + } catch(DbException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } catch(InterruptedException e) { + LOG.info("Interrupted while waiting for service"); + Thread.currentThread().interrupt(); + } + } + }); + } + + private void displayLocalAuthors( + final Collection<LocalAuthor> localAuthors) { + runOnUiThread(new Runnable() { + public void run() { + fromAdapter.clear(); + for(LocalAuthor a : localAuthors) + fromAdapter.add(new LocalAuthorItem(a)); + fromAdapter.sort(LocalAuthorItemComparator.INSTANCE); + fromAdapter.notifyDataSetChanged(); + } + }); + } + + private void loadLocalGroups() { + dbUiExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + long now = System.currentTimeMillis(); + Collection<LocalGroup> groups = db.getLocalGroups(); + long duration = System.currentTimeMillis() - now; + if(LOG.isLoggable(INFO)) + LOG.info("Loading groups took " + duration + " ms"); + displayLocalGroups(groups); + } catch(DbException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } catch(InterruptedException e) { + LOG.info("Interrupted while waiting for service"); + Thread.currentThread().interrupt(); + } + } + }); + } + + private void displayLocalGroups(final Collection<LocalGroup> groups) { + runOnUiThread(new Runnable() { + public void run() { + if(groups.isEmpty()) finish(); + int index = -1; + for(LocalGroup g : groups) { + if(g.getId().equals(localGroupId)) { + localGroup = g; + index = toAdapter.getCount(); + } + toAdapter.add(g); + } + if(index != -1) toSpinner.setSelection(index); + } + }); + } + + @Override + public void onSaveInstanceState(Bundle state) { + Parcelable p = content.onSaveInstanceState(); + state.putParcelable("net.sf.briar.CONTENT", p); + bundleEncrypter.encrypt(state); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unbindService(serviceConnection); + } + + public void onItemSelected(AdapterView<?> parent, View view, int position, + long id) { + if(parent == fromSpinner) { + LocalAuthorItem item = fromAdapter.getItem(position); + if(item == ANONYMOUS) { + localAuthor = null; + } else if(item == NEW) { + localAuthor = null; + startActivity(new Intent(this, CreateIdentityActivity.class)); + } else { + localAuthor = item.getLocalAuthor(); + } + } else if(parent == toSpinner) { + localGroup = toAdapter.getItem(position); + localGroupId = localGroup.getId(); + sendButton.setEnabled(true); + } + } + + public void onNothingSelected(AdapterView<?> parent) { + if(parent == fromSpinner) { + localAuthor = null; + } else if(parent == toSpinner) { + localGroup = null; + localGroupId = null; + sendButton.setEnabled(false); + } + } + + public void onClick(View view) { + if(localGroup == null) throw new IllegalStateException(); + try { + byte[] b = content.getText().toString().getBytes("UTF-8"); + storeMessage(createMessage(b)); + } catch(GeneralSecurityException e) { + throw new RuntimeException(e); + } catch(IOException e) { + throw new RuntimeException(e); + } + finish(); + } + + private Message createMessage(byte[] body) throws IOException, + GeneralSecurityException { + KeyParser keyParser = crypto.getSignatureKeyParser(); + byte[] groupKeyBytes = localGroup.getPrivateKey(); + PrivateKey groupKey = keyParser.parsePrivateKey(groupKeyBytes); + if(localAuthor == null) { + return messageFactory.createAnonymousMessage(parentId, localGroup, + groupKey, "text/plain", body); + } else { + byte[] authorKeyBytes = localAuthor.getPrivateKey(); + PrivateKey authorKey = keyParser.parsePrivateKey(authorKeyBytes); + return messageFactory.createPseudonymousMessage(parentId, + localGroup, groupKey, localAuthor, authorKey, "text/plain", + body); + } + } + + private void storeMessage(final Message m) { + dbUiExecutor.execute(new Runnable() { + public void run() { + try { + serviceConnection.waitForStartup(); + long now = System.currentTimeMillis(); + db.addLocalGroupMessage(m); + long duration = System.currentTimeMillis() - now; + if(LOG.isLoggable(INFO)) + LOG.info("Storing message 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(); + } + } + }); + } +} diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java similarity index 88% rename from briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java rename to briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java index b3c8e80230..18dbb71b88 100644 --- a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java @@ -12,8 +12,8 @@ import static net.sf.briar.android.identity.LocalAuthorItem.NEW; import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_WRAP; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; +import java.security.PrivateKey; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -33,6 +33,8 @@ import net.sf.briar.android.widgets.HorizontalSpace; import net.sf.briar.api.LocalAuthor; import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.DatabaseUiExecutor; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.crypto.KeyParser; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; import net.sf.briar.api.messaging.Group; @@ -55,16 +57,18 @@ import android.widget.TextView; import com.google.inject.Inject; -public class WriteGroupMessageActivity extends BriarActivity +public class WriteGroupPostActivity extends BriarActivity implements OnItemSelectedListener, OnClickListener { private static final Logger LOG = - Logger.getLogger(WriteGroupMessageActivity.class.getName()); + Logger.getLogger(WriteGroupPostActivity.class.getName()); private final BriarServiceConnection serviceConnection = new BriarServiceConnection(); @Inject private BundleEncrypter bundleEncrypter; + @Inject private CryptoComponent crypto; + @Inject private MessageFactory messageFactory; private LocalAuthorSpinnerAdapter fromAdapter = null; private GroupSpinnerAdapter toAdapter = null; private Spinner fromSpinner = null, toSpinner = null; @@ -74,8 +78,6 @@ implements OnItemSelectedListener, OnClickListener { // Fields that are accessed from background threads must be volatile @Inject private volatile DatabaseComponent db; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor; - @Inject private volatile MessageFactory messageFactory; - private volatile boolean restricted = false; private volatile LocalAuthor localAuthor = null; private volatile Group group = null; private volatile GroupId groupId = null; @@ -86,9 +88,6 @@ implements OnItemSelectedListener, OnClickListener { super.onCreate(null); Intent i = getIntent(); - restricted = i.getBooleanExtra("net.sf.briar.RESTRICTED", false); - if(restricted) setTitle(R.string.compose_blog_title); - else setTitle(R.string.compose_group_title); byte[] b = i.getByteArrayExtra("net.sf.briar.GROUP_ID"); if(b != null) groupId = new GroupId(b); b = i.getByteArrayExtra("net.sf.briar.PARENT_ID"); @@ -209,17 +208,12 @@ implements OnItemSelectedListener, OnClickListener { serviceConnection.waitForStartup(); List<Group> groups = new ArrayList<Group>(); long now = System.currentTimeMillis(); - if(restricted) { - groups.addAll(db.getLocalGroups()); - } else { - for(Group g : db.getSubscriptions()) - if(!g.isRestricted()) groups.add(g); - } + for(Group g : db.getSubscriptions()) + if(!g.isRestricted()) groups.add(g); long duration = System.currentTimeMillis() - now; if(LOG.isLoggable(INFO)) LOG.info("Loading groups took " + duration + " ms"); - groups = Collections.unmodifiableList(groups); - displayGroups(groups); + displayGroups(Collections.unmodifiableList(groups)); } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -294,22 +288,34 @@ implements OnItemSelectedListener, OnClickListener { if(group == null) throw new IllegalStateException(); try { byte[] b = content.getText().toString().getBytes("UTF-8"); - storeMessage(localAuthor, group, b); - } catch(UnsupportedEncodingException e) { + storeMessage(createMessage(b)); + } catch(GeneralSecurityException e) { + throw new RuntimeException(e); + } catch(IOException e) { throw new RuntimeException(e); } finish(); } - private void storeMessage(final LocalAuthor localAuthor, final Group group, - final byte[] body) { + private Message createMessage(byte[] body) throws IOException, + GeneralSecurityException { + if(localAuthor == null) { + return messageFactory.createAnonymousMessage(parentId, group, + "text/plain", body); + } else { + KeyParser keyParser = crypto.getSignatureKeyParser(); + byte[] authorKeyBytes = localAuthor.getPrivateKey(); + PrivateKey authorKey = keyParser.parsePrivateKey(authorKeyBytes); + return messageFactory.createPseudonymousMessage(parentId, + group, localAuthor, authorKey, "text/plain", body); + } + } + + private void storeMessage(final Message m) { dbUiExecutor.execute(new Runnable() { public void run() { try { serviceConnection.waitForStartup(); - // FIXME: Anonymous/pseudonymous, restricted/unrestricted - Message m = messageFactory.createAnonymousMessage(parentId, - group, "text/plain", body); long now = System.currentTimeMillis(); db.addLocalGroupMessage(m); long duration = System.currentTimeMillis() - now; @@ -318,14 +324,10 @@ implements OnItemSelectedListener, OnClickListener { } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } catch(GeneralSecurityException e) { - throw new RuntimeException(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); } } }); -- GitLab