diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 0b0771da6..e402626af 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -40,6 +40,22 @@
android:name=".android.contact.ContactListActivity"
android:label="@string/contact_list_title" >
+
+
+
+
+
+
+
+
@@ -53,12 +69,12 @@
android:label="@string/messages_title" >
+ android:name="net.sf.briar.android.messages.WritePrivateMessageActivity"
+ android:label="@string/compose_message_title" >
diff --git a/briar-android/res/drawable-hdpi/social_new_chat.png b/briar-android/res/drawable-hdpi/social_new_chat.png
new file mode 100644
index 000000000..a6a42eeb6
Binary files /dev/null and b/briar-android/res/drawable-hdpi/social_new_chat.png differ
diff --git a/briar-android/res/drawable-hdpi/social_reply_all.png b/briar-android/res/drawable-hdpi/social_reply_all.png
new file mode 100644
index 000000000..377f6286c
Binary files /dev/null and b/briar-android/res/drawable-hdpi/social_reply_all.png differ
diff --git a/briar-android/res/drawable-mdpi/social_new_chat.png b/briar-android/res/drawable-mdpi/social_new_chat.png
new file mode 100644
index 000000000..e78580b8e
Binary files /dev/null and b/briar-android/res/drawable-mdpi/social_new_chat.png differ
diff --git a/briar-android/res/drawable-mdpi/social_reply_all.png b/briar-android/res/drawable-mdpi/social_reply_all.png
new file mode 100644
index 000000000..86334552e
Binary files /dev/null and b/briar-android/res/drawable-mdpi/social_reply_all.png differ
diff --git a/briar-android/res/drawable-xhdpi/social_new_chat.png b/briar-android/res/drawable-xhdpi/social_new_chat.png
new file mode 100644
index 000000000..9d5d90492
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/social_new_chat.png differ
diff --git a/briar-android/res/drawable-xhdpi/social_reply_all.png b/briar-android/res/drawable-xhdpi/social_reply_all.png
new file mode 100644
index 000000000..f10a492c0
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/social_reply_all.png differ
diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml
index a55aadfa9..866b33e58 100644
--- a/briar-android/res/values/color.xml
+++ b/briar-android/res/values/color.xml
@@ -1,4 +1,6 @@
- #CCCCCC
+ #CCCCCC
+ #999999
+ #000000
\ No newline at end of file
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 92486abf3..14258e047 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -11,14 +11,13 @@
Quit
Contacts
Connected
- Last connected <br /> %1$s
- Search
+ Last connected <br /> %1$s
Add a Contact
Briar can add contacts via Wi-Fi or Bluetooth. To use Wi-Fi you must both be connected to the same network.
Wi-Fi is not available on this device
Wi-Fi is OFF
Wi-Fi is DISCONNECTED
- Wi-Fi is CONNECTED to %1$s
+ Wi-Fi is CONNECTED to %1$s
Bluetooth is not available on this device
Bluetooth is OFF
Bluetooth is NOT DISCOVERABLE
@@ -26,7 +25,7 @@
Continue
Your invitation code is
Please enter your contact\'s invitation code:
- Connecting via %1$s\u2026
+ Connecting via %1$s\u2026
Connecting via Bluetooth\u2026
Connection failed
Please check that you are both using the same network.
@@ -41,7 +40,11 @@
Please enter a nickname for this contact:
Done
Messages
- From: %1$s
- New Message
- To:
+ From: %1$s
+ To: %1$s
+ New Message
+ To:
+ Groups
+ (Anonymous)
+ New Post
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index 090ceca84..02cea07ef 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -12,6 +12,7 @@ import net.sf.briar.R;
import net.sf.briar.android.BriarService.BriarBinder;
import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.contact.ContactListActivity;
+import net.sf.briar.android.groups.GroupListActivity;
import net.sf.briar.android.messages.ConversationListActivity;
import net.sf.briar.android.widgets.CommonLayoutParams;
import android.content.Intent;
@@ -88,7 +89,8 @@ public class HomeScreenActivity extends BriarActivity {
groupsButton.setText(R.string.groups_button);
groupsButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
- // FIXME: Hook this button up to an activity
+ startActivity(new Intent(HomeScreenActivity.this,
+ GroupListActivity.class));
}
});
buttons.add(groupsButton);
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactComparator.java b/briar-android/src/net/sf/briar/android/contact/ContactComparator.java
index 6495bf631..8e9207c6f 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactComparator.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactComparator.java
@@ -7,7 +7,7 @@ class ContactComparator implements Comparator {
static final ContactComparator INSTANCE = new ContactComparator();
public int compare(ContactListItem a, ContactListItem b) {
- return String.CASE_INSENSITIVE_ORDER.compare(a.contact.getName(),
- b.contact.getName());
+ return String.CASE_INSENSITIVE_ORDER.compare(a.getContactName(),
+ b.getContactName());
}
}
\ No newline at end of file
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
index 39cb91c6c..bb10e399c 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListAdapter.java
@@ -8,7 +8,6 @@ import java.util.ArrayList;
import net.sf.briar.R;
import net.sf.briar.android.widgets.CommonLayoutParams;
import android.content.Context;
-import android.content.res.Resources;
import android.text.Html;
import android.text.format.DateUtils;
import android.view.View;
@@ -46,8 +45,9 @@ implements OnItemClickListener {
// Give me all the unused width
name.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
name.setTextSize(18);
+ name.setMaxLines(1);
name.setPadding(0, 10, 10, 10);
- name.setText(item.getName());
+ name.setText(item.getContactName());
layout.addView(name);
TextView connected = new TextView(ctx);
@@ -56,8 +56,8 @@ implements OnItemClickListener {
if(item.isConnected()) {
connected.setText(R.string.contact_connected);
} else {
- Resources res = ctx.getResources();
- String format = res.getString(R.string.contact_last_connected);
+ String format = ctx.getResources().getString(
+ R.string.format_contact_last_connected);
long then = item.getLastConnected();
CharSequence ago = DateUtils.getRelativeTimeSpanString(then);
connected.setText(Html.fromHtml(String.format(format, ago)));
diff --git a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
index cba805e86..0b030f2f9 100644
--- a/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
+++ b/briar-android/src/net/sf/briar/android/contact/ContactListItem.java
@@ -6,7 +6,7 @@ import net.sf.briar.api.ContactId;
// This class is not thread-safe
class ContactListItem {
- final Contact contact;
+ private final Contact contact;
private boolean connected;
ContactListItem(Contact contact, boolean connected) {
@@ -18,7 +18,7 @@ class ContactListItem {
return contact.getId();
}
- String getName() {
+ String getContactName() {
return contact.getName();
}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
new file mode 100644
index 000000000..8ed287306
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -0,0 +1,226 @@
+package net.sf.briar.android.groups;
+
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import net.sf.briar.R;
+import net.sf.briar.android.AscendingHeaderComparator;
+import net.sf.briar.android.BriarActivity;
+import net.sf.briar.android.BriarService;
+import net.sf.briar.android.BriarService.BriarServiceConnection;
+import net.sf.briar.android.widgets.CommonLayoutParams;
+import net.sf.briar.android.widgets.HorizontalBorder;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseExecutor;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.db.event.DatabaseEvent;
+import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.db.event.MessageAddedEvent;
+import net.sf.briar.api.db.event.MessageExpiredEvent;
+import net.sf.briar.api.db.event.SubscriptionAddedEvent;
+import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.GroupId;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import com.google.inject.Inject;
+
+public class GroupActivity extends BriarActivity implements DatabaseListener,
+OnClickListener, OnItemClickListener {
+
+ private static final Logger LOG =
+ Logger.getLogger(GroupActivity.class.getName());
+
+ private final BriarServiceConnection serviceConnection =
+ new BriarServiceConnection();
+
+ @Inject private DatabaseComponent db;
+ @Inject @DatabaseExecutor private Executor dbExecutor;
+
+ private GroupId groupId = null;
+ private String groupName = null;
+ private GroupAdapter adapter = null;
+ private ListView list = null;
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(null);
+
+ Intent i = getIntent();
+ byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
+ if(id == null) throw new IllegalStateException();
+ groupId = new GroupId(id);
+ groupName = i.getStringExtra("net.sf.briar.GROUP_NAME");
+ if(groupName == null) throw new IllegalStateException();
+ setTitle(groupName);
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH);
+ layout.setOrientation(VERTICAL);
+ layout.setGravity(CENTER_HORIZONTAL);
+
+ adapter = new GroupAdapter(this);
+ list = new ListView(this);
+ // Give me all the width and all the unused height
+ list.setLayoutParams(CommonLayoutParams.MATCH_WRAP_1);
+ list.setAdapter(adapter);
+ list.setOnItemClickListener(this);
+ layout.addView(list);
+
+ layout.addView(new HorizontalBorder(this));
+
+ ImageButton composeButton = new ImageButton(this);
+ composeButton.setBackgroundResource(0);
+ composeButton.setImageResource(R.drawable.content_new_email);
+ composeButton.setOnClickListener(this);
+ layout.addView(composeButton);
+
+ setContentView(layout);
+
+ // Listen for messages and groups being added or removed
+ db.addListener(this);
+ // Bind to the service so we can wait for the DB to be opened
+ bindService(new Intent(BriarService.class.getName()),
+ serviceConnection, 0);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ reloadMessageHeaders();
+ }
+
+ private void reloadMessageHeaders() {
+ final DatabaseComponent db = this.db;
+ final GroupId groupId = this.groupId;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ // Wait for the service to be bound and started
+ serviceConnection.waitForStartup();
+ // Load the message headers from the database
+ Collection headers =
+ db.getMessageHeaders(groupId);
+ if(LOG.isLoggable(INFO))
+ LOG.info("Loaded " + headers.size() + " headers");
+ // Update the conversation
+ updateConversation(headers);
+ } 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 updateConversation(
+ final Collection headers) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ List sort =
+ new ArrayList(headers);
+ Collections.sort(sort, AscendingHeaderComparator.INSTANCE);
+ int firstUnread = -1;
+ adapter.clear();
+ for(GroupMessageHeader h : sort) {
+ if(firstUnread == -1 && !h.isRead())
+ firstUnread = adapter.getCount();
+ adapter.add(h);
+ }
+ if(firstUnread == -1) list.setSelection(adapter.getCount() - 1);
+ else list.setSelection(firstUnread);
+ }
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ db.removeListener(this);
+ unbindService(serviceConnection);
+ }
+
+ public void eventOccurred(DatabaseEvent e) {
+ if(e instanceof MessageAddedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
+ reloadMessageHeaders();
+ } else if(e instanceof MessageExpiredEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
+ reloadMessageHeaders();
+ } else if(e instanceof SubscriptionAddedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
+ reloadMessageHeaders();
+ } else if(e instanceof SubscriptionRemovedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
+ reloadMessageHeaders();
+ }
+ }
+
+ public void onClick(View view) {
+ Intent i = new Intent(this, WriteGroupMessageActivity.class);
+ i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
+ i.putExtra("net.sf.briar.GROUP_NAME", groupName);
+ startActivity(i);
+ }
+
+ public void onItemClick(AdapterView> parent, View view, int position,
+ long id) {
+ showMessage(position);
+ }
+
+ private void showMessage(int position) {
+ GroupMessageHeader item = adapter.getItem(position);
+ Intent i = new Intent(this, ReadGroupMessageActivity.class);
+ i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
+ i.putExtra("net.sf.briar.GROUP_NAME", groupName);
+ i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes());
+ Author author = item.getAuthor();
+ if(author == null) {
+ i.putExtra("net.sf.briar.ANONYMOUS", true);
+ } else {
+ i.putExtra("net.sf.briar.ANONYMOUS", false);
+ i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes());
+ i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName());
+ }
+ i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType());
+ i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp());
+ i.putExtra("net.sf.briar.FIRST", position == 0);
+ i.putExtra("net.sf.briar.LAST", position == adapter.getCount() - 1);
+ startActivityForResult(i, position);
+ }
+
+ @Override
+ public void onActivityResult(int request, int result, Intent data) {
+ if(result == ReadGroupMessageActivity.RESULT_PREV) {
+ int position = request - 1;
+ if(position >= 0 && position < adapter.getCount())
+ showMessage(position);
+ } else if(result == ReadGroupMessageActivity.RESULT_NEXT) {
+ int position = request + 1;
+ if(position >= 0 && position < adapter.getCount())
+ showMessage(position);
+ }
+ }
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
new file mode 100644
index 000000000..17915c8d4
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupAdapter.java
@@ -0,0 +1,92 @@
+package net.sf.briar.android.groups;
+
+import static android.graphics.Typeface.BOLD;
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.text.DateFormat.SHORT;
+
+import java.util.ArrayList;
+
+import net.sf.briar.R;
+import net.sf.briar.android.widgets.CommonLayoutParams;
+import net.sf.briar.android.widgets.HorizontalSpace;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.messaging.Author;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+class GroupAdapter extends ArrayAdapter {
+
+ GroupAdapter(Context ctx) {
+ super(ctx, android.R.layout.simple_expandable_list_item_1,
+ new ArrayList());
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ GroupMessageHeader item = getItem(position);
+ Context ctx = getContext();
+ // FIXME: Use a RelativeLayout
+ LinearLayout layout = new LinearLayout(ctx);
+ layout.setOrientation(HORIZONTAL);
+ layout.setGravity(CENTER_VERTICAL);
+
+ LinearLayout innerLayout = new LinearLayout(ctx);
+ // Give me all the unused width
+ innerLayout.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
+ innerLayout.setOrientation(VERTICAL);
+
+ Author author = item.getAuthor();
+
+ TextView name = new TextView(ctx);
+ name.setTextSize(18);
+ name.setMaxLines(1);
+ name.setPadding(10, 10, 10, 10);
+ Resources res = ctx.getResources();
+ if(author == null) {
+ name.setTextColor(res.getColor(R.color.anonymous_author));
+ name.setText(R.string.anonymous);
+ } else {
+ name.setTextColor(res.getColor(R.color.pseudonymous_author));
+ name.setText(author.getName());
+ }
+ innerLayout.addView(name);
+
+ if(item.getContentType().equals("text/plain")) {
+ TextView subject = new TextView(ctx);
+ subject.setTextSize(14);
+ subject.setMaxLines(2);
+ subject.setPadding(10, 0, 10, 10);
+ if(!item.isRead()) subject.setTypeface(null, BOLD);
+ subject.setText(item.getSubject());
+ innerLayout.addView(subject);
+ } else {
+ LinearLayout innerInnerLayout = new LinearLayout(ctx);
+ innerInnerLayout.setOrientation(HORIZONTAL);
+ ImageView attachment = new ImageView(ctx);
+ attachment.setPadding(10, 0, 10, 10);
+ attachment.setImageResource(R.drawable.content_attachment);
+ innerInnerLayout.addView(attachment);
+ innerInnerLayout.addView(new HorizontalSpace(ctx));
+ innerLayout.addView(innerInnerLayout);
+ }
+ layout.addView(innerLayout);
+
+ TextView date = new TextView(ctx);
+ date.setTextSize(14);
+ date.setPadding(0, 10, 10, 10);
+ long then = item.getTimestamp(), now = System.currentTimeMillis();
+ date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
+ layout.addView(date);
+
+ return layout;
+ }
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
new file mode 100644
index 000000000..5c0c3beec
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -0,0 +1,302 @@
+package net.sf.briar.android.groups;
+
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+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.DescendingHeaderComparator;
+import net.sf.briar.android.widgets.CommonLayoutParams;
+import net.sf.briar.android.widgets.HorizontalBorder;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.crypto.CryptoComponent;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseExecutor;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.db.NoSuchSubscriptionException;
+import net.sf.briar.api.db.event.DatabaseEvent;
+import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.db.event.MessageAddedEvent;
+import net.sf.briar.api.db.event.MessageExpiredEvent;
+import net.sf.briar.api.db.event.SubscriptionAddedEvent;
+import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.AuthorFactory;
+import net.sf.briar.api.messaging.Group;
+import net.sf.briar.api.messaging.GroupFactory;
+import net.sf.briar.api.messaging.Message;
+import net.sf.briar.api.messaging.MessageFactory;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import com.google.inject.Inject;
+
+public class GroupListActivity extends BriarActivity
+implements OnClickListener, DatabaseListener {
+
+ private static final Logger LOG =
+ Logger.getLogger(GroupListActivity.class.getName());
+
+ private final BriarServiceConnection serviceConnection =
+ new BriarServiceConnection();
+
+ @Inject private CryptoComponent crypto;
+ @Inject private DatabaseComponent db;
+ @Inject @DatabaseExecutor private Executor dbExecutor;
+ @Inject private AuthorFactory authorFactory;
+ @Inject private GroupFactory groupFactory;
+ @Inject private MessageFactory messageFactory;
+
+ private GroupListAdapter adapter = null;
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(null);
+ LinearLayout layout = new LinearLayout(this);
+ layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH);
+ layout.setOrientation(VERTICAL);
+ layout.setGravity(CENTER_HORIZONTAL);
+
+ adapter = new GroupListAdapter(this);
+ ListView list = new ListView(this);
+ // Give me all the width and all the unused height
+ list.setLayoutParams(CommonLayoutParams.MATCH_WRAP_1);
+ list.setAdapter(adapter);
+ list.setOnItemClickListener(adapter);
+ layout.addView(list);
+
+ layout.addView(new HorizontalBorder(this));
+
+ ImageButton newGroupButton = new ImageButton(this);
+ newGroupButton.setBackgroundResource(0);
+ newGroupButton.setImageResource(R.drawable.social_new_chat);
+ newGroupButton.setOnClickListener(this);
+ layout.addView(newGroupButton);
+
+ setContentView(layout);
+
+ // Listen for messages and groups being added or removed
+ db.addListener(this);
+ // Bind to the service so we can wait for the DB to be opened
+ bindService(new Intent(BriarService.class.getName()),
+ serviceConnection, 0);
+
+ // Add some fake messages to the database in a background thread
+ insertFakeMessages();
+ }
+
+ // FIXME: Remove this
+ private void insertFakeMessages() {
+ final DatabaseComponent db = this.db;
+ final GroupFactory groupFactory = this.groupFactory;
+ final MessageFactory messageFactory = this.messageFactory;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ // Wait for the service to be bound and started
+ serviceConnection.waitForStartup();
+ // If there are no groups in the DB, create some fake ones
+ Collection groups = db.getSubscriptions();
+ if(!groups.isEmpty()) return;
+ if(LOG.isLoggable(INFO))
+ LOG.info("Inserting fake groups and messages");
+ // We'll also need a contact to receive messages from
+ ContactId contactId = db.addContact("Dave");
+ // Finally, we'll need some authors for the messages
+ KeyPair keyPair = crypto.generateSignatureKeyPair();
+ byte[] publicKey = keyPair.getPublic().getEncoded();
+ PrivateKey privateKey = keyPair.getPrivate();
+ Author author = authorFactory.createAuthor("Batman",
+ publicKey);
+ Author author1 = authorFactory.createAuthor("Duckman",
+ publicKey);
+ // Insert some fake groups and make them visible
+ Group group = groupFactory.createGroup("DisneyLeaks");
+ db.subscribe(group);
+ db.setVisibility(group.getId(), Arrays.asList(contactId));
+ Group group1 = groupFactory.createGroup("Godwin's Lore");
+ db.subscribe(group1);
+ db.setVisibility(group1.getId(), Arrays.asList(contactId));
+ // Insert some text messages to the groups
+ for(int i = 0; i < 20; i++) {
+ String body;
+ if(i % 3 == 0) {
+ body = "Message " + i + " is short.";
+ } else {
+ body = "Message " + i + " is long enough to wrap"
+ + " onto a second line on some screens.";
+ }
+ Group g = i % 2 == 0 ? group : group1;
+ Message m;
+ if(i % 5 == 0) {
+ m = messageFactory.createAnonymousMessage(null, g,
+ "text/plain", body.getBytes("UTF-8"));
+ } else if(i % 5 == 2) {
+ m = messageFactory.createPseudonymousMessage(null,
+ g, author, privateKey, "text/plain",
+ body.getBytes("UTF-8"));
+ } else {
+ m = messageFactory.createPseudonymousMessage(null,
+ g, author1, privateKey, "text/plain",
+ body.getBytes("UTF-8"));
+ }
+ if(Math.random() < 0.5) db.addLocalGroupMessage(m);
+ else db.receiveMessage(contactId, m);
+ db.setReadFlag(m.getId(), i % 4 == 0);
+ }
+ // Insert a non-text message
+ Message m = messageFactory.createAnonymousMessage(null,
+ group, "image/jpeg", new byte[1000]);
+ db.receiveMessage(contactId, m);
+ // Insert a long text message
+ StringBuilder s = new StringBuilder();
+ for(int i = 0; i < 100; i++)
+ s.append("This is a very tedious message. ");
+ String body = s.toString();
+ m = messageFactory.createAnonymousMessage(m.getId(),
+ group1, "text/plain", body.getBytes("UTF-8"));
+ db.addLocalGroupMessage(m);
+ } catch(DbException e) {
+ if(LOG.isLoggable(WARNING))
+ LOG.log(WARNING, e.toString(), e);
+ } catch(GeneralSecurityException 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) {
+ if(LOG.isLoggable(WARNING))
+ LOG.log(WARNING, e.toString(), e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ reloadGroupList();
+ }
+
+ private void reloadGroupList() {
+ final DatabaseComponent db = this.db;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ // Wait for the service to be bound and started
+ serviceConnection.waitForStartup();
+ // Load the groups and message headers from the DB
+ if(LOG.isLoggable(INFO)) LOG.info("Loading groups");
+ Collection groups = db.getSubscriptions();
+ if(LOG.isLoggable(INFO))
+ LOG.info("Loaded " + groups.size() + " groups");
+ List items = new ArrayList();
+ for(Group g : groups) {
+ // Filter out restricted groups
+ if(g.getPublicKey() != null) continue;
+ Collection headers;
+ try {
+ headers = db.getMessageHeaders(g.getId());
+ } catch(NoSuchSubscriptionException e) {
+ // We'll reload the list when we get the event
+ continue;
+ }
+ if(LOG.isLoggable(INFO))
+ LOG.info("Loaded " + headers.size() + " headers");
+ if(!headers.isEmpty())
+ items.add(createItem(g, headers));
+ }
+ // Update the group list
+ updateGroupList(Collections.unmodifiableList(items));
+ } 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 GroupListItem createItem(Group group,
+ Collection headers) {
+ List sort =
+ new ArrayList(headers);
+ Collections.sort(sort, DescendingHeaderComparator.INSTANCE);
+ return new GroupListItem(group, sort);
+ }
+
+ private void updateGroupList(final Collection items) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ adapter.clear();
+ for(GroupListItem i : items) adapter.add(i);
+ adapter.sort(GroupComparator.INSTANCE);
+ }
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ db.removeListener(this);
+ unbindService(serviceConnection);
+ }
+
+ public void onClick(View view) {
+ startActivity(new Intent(this, WriteGroupMessageActivity.class));
+ }
+
+ public void eventOccurred(DatabaseEvent e) {
+ if(e instanceof MessageAddedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
+ reloadGroupList();
+ } else if(e instanceof MessageExpiredEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
+ reloadGroupList();
+ } else if(e instanceof SubscriptionAddedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
+ reloadGroupList();
+ } else if(e instanceof SubscriptionRemovedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
+ reloadGroupList();
+ }
+ }
+
+ private static class GroupComparator implements Comparator {
+
+ private static final GroupComparator INSTANCE = new GroupComparator();
+
+ public int compare(GroupListItem a, GroupListItem b) {
+ return String.CASE_INSENSITIVE_ORDER.compare(a.getGroupName(),
+ b.getGroupName());
+ }
+ }
+}
\ No newline at end of file
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java
new file mode 100644
index 000000000..102a93a06
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListAdapter.java
@@ -0,0 +1,85 @@
+package net.sf.briar.android.groups;
+
+import static android.graphics.Typeface.BOLD;
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.Gravity.LEFT;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.text.DateFormat.SHORT;
+
+import java.util.ArrayList;
+
+import net.sf.briar.android.widgets.CommonLayoutParams;
+import net.sf.briar.util.StringUtils;
+import android.content.Context;
+import android.content.Intent;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+class GroupListAdapter extends ArrayAdapter
+implements OnItemClickListener {
+
+ GroupListAdapter(Context ctx) {
+ super(ctx, android.R.layout.simple_expandable_list_item_1,
+ new ArrayList());
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ GroupListItem item = getItem(position);
+ Context ctx = getContext();
+ LinearLayout layout = new LinearLayout(ctx);
+ layout.setOrientation(HORIZONTAL);
+ layout.setGravity(CENTER_VERTICAL);
+
+ LinearLayout innerLayout = new LinearLayout(ctx);
+ // Give me all the unused width
+ innerLayout.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
+ innerLayout.setOrientation(VERTICAL);
+ innerLayout.setGravity(LEFT);
+
+ TextView name = new TextView(ctx);
+ name.setTextSize(18);
+ name.setMaxLines(1);
+ name.setPadding(10, 10, 10, 10);
+ int unread = item.getUnreadCount();
+ if(unread > 0) name.setText(item.getGroupName() + " (" + unread + ")");
+ else name.setText(item.getGroupName());
+ innerLayout.addView(name);
+
+ if(!StringUtils.isNullOrEmpty(item.getSubject())) {
+ TextView subject = new TextView(ctx);
+ subject.setTextSize(14);
+ subject.setMaxLines(2);
+ subject.setPadding(10, 0, 10, 10);
+ if(unread > 0) subject.setTypeface(null, BOLD);
+ subject.setText(item.getSubject());
+ innerLayout.addView(subject);
+ }
+ layout.addView(innerLayout);
+
+ TextView date = new TextView(ctx);
+ date.setTextSize(14);
+ date.setPadding(0, 10, 10, 10);
+ long then = item.getTimestamp(), now = System.currentTimeMillis();
+ date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
+ layout.addView(date);
+
+ return layout;
+ }
+
+ public void onItemClick(AdapterView> parent, View view, int position,
+ long id) {
+ GroupListItem item = getItem(position);
+ Intent i = new Intent(getContext(), GroupActivity.class);
+ i.putExtra("net.sf.briar.GROUP_ID", item.getGroupId().getBytes());
+ i.putExtra("net.sf.briar.GROUP_NAME", item.getGroupName());
+ getContext().startActivity(i);
+ }
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListItem.java b/briar-android/src/net/sf/briar/android/groups/GroupListItem.java
new file mode 100644
index 000000000..38a4513c2
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListItem.java
@@ -0,0 +1,57 @@
+package net.sf.briar.android.groups;
+
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.briar.android.DescendingHeaderComparator;
+import net.sf.briar.api.db.GroupMessageHeader;
+import net.sf.briar.api.messaging.Author;
+import net.sf.briar.api.messaging.Group;
+import net.sf.briar.api.messaging.GroupId;
+
+class GroupListItem {
+
+ private final Group group;
+ private final String author, subject;
+ private final long timestamp;
+ private final int unread;
+
+ GroupListItem(Group group, List headers) {
+ if(headers.isEmpty()) throw new IllegalArgumentException();
+ this.group = group;
+ Collections.sort(headers, DescendingHeaderComparator.INSTANCE);
+ GroupMessageHeader newest = headers.get(0);
+ Author a = newest.getAuthor();
+ if(a == null) author = null;
+ else author = a.getName();
+ subject = newest.getSubject();
+ timestamp = newest.getTimestamp();
+ int unread = 0;
+ for(GroupMessageHeader h : headers) if(!h.isRead()) unread++;
+ this.unread = unread;
+ }
+
+ GroupId getGroupId() {
+ return group.getId();
+ }
+
+ String getGroupName() {
+ return group.getName();
+ }
+
+ String getAuthorName() {
+ return author;
+ }
+
+ String getSubject() {
+ return subject;
+ }
+
+ long getTimestamp() {
+ return timestamp;
+ }
+
+ int getUnreadCount() {
+ return unread;
+ }
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java b/briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java
new file mode 100644
index 000000000..2c16ebfaf
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/GroupNameSpinnerAdapter.java
@@ -0,0 +1,36 @@
+package net.sf.briar.android.groups;
+
+import java.util.ArrayList;
+
+import net.sf.briar.api.messaging.Group;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+class GroupNameSpinnerAdapter extends ArrayAdapter
+implements SpinnerAdapter {
+
+ GroupNameSpinnerAdapter(Context context) {
+ super(context, android.R.layout.simple_spinner_item,
+ new ArrayList());
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView name = new TextView(getContext());
+ name.setTextSize(18);
+ name.setMaxLines(1);
+ name.setPadding(10, 10, 10, 10);
+ 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
new file mode 100644
index 000000000..d5750019b
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java
@@ -0,0 +1,288 @@
+package net.sf.briar.android.groups;
+
+import static android.view.Gravity.CENTER;
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+import static java.text.DateFormat.SHORT;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+import java.io.UnsupportedEncodingException;
+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.widgets.CommonLayoutParams;
+import net.sf.briar.android.widgets.HorizontalBorder;
+import net.sf.briar.android.widgets.HorizontalSpace;
+import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseExecutor;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.messaging.AuthorId;
+import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.MessageId;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.google.inject.Inject;
+
+public class ReadGroupMessageActivity extends BriarActivity
+implements OnClickListener {
+
+ static final int RESULT_REPLY = RESULT_FIRST_USER;
+ static final int RESULT_PREV = RESULT_FIRST_USER + 1;
+ static final int RESULT_NEXT = RESULT_FIRST_USER + 2;
+
+ private static final Logger LOG =
+ Logger.getLogger(ReadGroupMessageActivity.class.getName());
+
+ private final BriarServiceConnection serviceConnection =
+ new BriarServiceConnection();
+
+ @Inject private BundleEncrypter bundleEncrypter;
+ @Inject private DatabaseComponent db;
+ @Inject @DatabaseExecutor private Executor dbExecutor;
+
+ private GroupId groupId = null;
+ private MessageId messageId = null;
+ private AuthorId authorId = null;
+ private String authorName = null;
+ private boolean read;
+ private ImageButton readButton = null, prevButton = null, nextButton = null;
+ private ImageButton replyButton = null;
+ private TextView content = null;
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(null);
+
+ Intent i = getIntent();
+ byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
+ if(id == null) throw new IllegalStateException();
+ groupId = new GroupId(id);
+ String groupName = i.getStringExtra("net.sf.briar.GROUP_NAME");
+ if(groupName == null) throw new IllegalStateException();
+ setTitle(groupName);
+ id = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
+ if(id == null) throw new IllegalStateException();
+ messageId = new MessageId(id);
+ boolean anonymous = i.getBooleanExtra("net.sf.briar.ANONYMOUS", false);
+ if(!anonymous) {
+ id = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID");
+ if(id == null) throw new IllegalStateException();
+ authorId = new AuthorId(id);
+ authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME");
+ if(authorName == null) throw new IllegalStateException();
+ }
+ String contentType = i.getStringExtra("net.sf.briar.CONTENT_TYPE");
+ if(contentType == null) throw new IllegalStateException();
+ long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
+ if(timestamp == -1) throw new IllegalStateException();
+ boolean first = i.getBooleanExtra("net.sf.briar.FIRST", false);
+ boolean last = i.getBooleanExtra("net.sf.briar.LAST", false);
+
+ if(state != null && bundleEncrypter.decrypt(state)) {
+ read = state.getBoolean("net.sf.briar.READ");
+ } else {
+ read = false;
+ setReadInDatabase(true);
+ }
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+ layout.setOrientation(VERTICAL);
+
+ ScrollView scrollView = new ScrollView(this);
+ // Give me all the width and all the unused height
+ scrollView.setLayoutParams(CommonLayoutParams.MATCH_WRAP_1);
+
+ LinearLayout message = new LinearLayout(this);
+ message.setOrientation(VERTICAL);
+
+ LinearLayout header = new LinearLayout(this);
+ header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+ header.setOrientation(HORIZONTAL);
+ header.setGravity(CENTER_VERTICAL);
+
+ TextView author = new TextView(this);
+ // Give me all the unused width
+ author.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
+ author.setTextSize(18);
+ author.setMaxLines(1);
+ author.setPadding(10, 10, 10, 10);
+ Resources res = getResources();
+ if(authorName == null) {
+ author.setTextColor(res.getColor(R.color.anonymous_author));
+ author.setText(R.string.anonymous);
+ } else {
+ author.setTextColor(res.getColor(R.color.pseudonymous_author));
+ author.setText(authorName);
+ }
+ header.addView(author);
+
+ TextView date = new TextView(this);
+ date.setTextSize(14);
+ date.setPadding(0, 10, 10, 10);
+ long now = System.currentTimeMillis();
+ date.setText(DateUtils.formatSameDayTime(timestamp, now, SHORT, SHORT));
+ header.addView(date);
+ message.addView(header);
+
+ if(contentType.equals("text/plain")) {
+ // Load and display the message body
+ content = new TextView(this);
+ content.setPadding(10, 0, 10, 10);
+ message.addView(content);
+ loadMessageBody();
+ }
+ scrollView.addView(message);
+ layout.addView(scrollView);
+
+ layout.addView(new HorizontalBorder(this));
+
+ LinearLayout footer = new LinearLayout(this);
+ footer.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+ footer.setOrientation(HORIZONTAL);
+ footer.setGravity(CENTER);
+
+ readButton = new ImageButton(this);
+ readButton.setBackgroundResource(0);
+ if(read) readButton.setImageResource(R.drawable.content_unread);
+ else readButton.setImageResource(R.drawable.content_read);
+ readButton.setOnClickListener(this);
+ footer.addView(readButton);
+ footer.addView(new HorizontalSpace(this));
+
+ prevButton = new ImageButton(this);
+ prevButton.setBackgroundResource(0);
+ prevButton.setImageResource(R.drawable.navigation_previous_item);
+ prevButton.setOnClickListener(this);
+ prevButton.setEnabled(!first);
+ footer.addView(prevButton);
+ footer.addView(new HorizontalSpace(this));
+
+ nextButton = new ImageButton(this);
+ nextButton.setBackgroundResource(0);
+ nextButton.setImageResource(R.drawable.navigation_next_item);
+ nextButton.setOnClickListener(this);
+ nextButton.setEnabled(!last);
+ footer.addView(nextButton);
+ footer.addView(new HorizontalSpace(this));
+
+ replyButton = new ImageButton(this);
+ replyButton.setBackgroundResource(0);
+ replyButton.setImageResource(R.drawable.social_reply_all);
+ replyButton.setOnClickListener(this);
+ footer.addView(replyButton);
+ layout.addView(footer);
+
+ setContentView(layout);
+
+ // Bind to the service so we can wait for the DB to be opened
+ bindService(new Intent(BriarService.class.getName()),
+ serviceConnection, 0);
+ }
+
+ private void setReadInDatabase(final boolean read) {
+ final DatabaseComponent db = this.db;
+ final MessageId messageId = this.messageId;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ serviceConnection.waitForStartup();
+ db.setReadFlag(messageId, read);
+ setReadInUi(read);
+ } 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 setReadInUi(final boolean read) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ ReadGroupMessageActivity.this.read = read;
+ if(read) readButton.setImageResource(R.drawable.content_unread);
+ else readButton.setImageResource(R.drawable.content_read);
+ }
+ });
+ }
+
+ private void loadMessageBody() {
+ final DatabaseComponent db = this.db;
+ final MessageId messageId = this.messageId;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ serviceConnection.waitForStartup();
+ byte[] body = db.getMessageBody(messageId);
+ final String text = new String(body, "UTF-8");
+ runOnUiThread(new Runnable() {
+ public void run() {
+ content.setText(text);
+ }
+ });
+ } 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(UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle state) {
+ state.putBoolean("net.sf.briar.READ", read);
+ bundleEncrypter.encrypt(state);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unbindService(serviceConnection);
+ }
+
+ public void onClick(View view) {
+ if(view == readButton) {
+ setReadInDatabase(!read);
+ } else if(view == prevButton) {
+ setResult(RESULT_PREV);
+ finish();
+ } else if(view == nextButton) {
+ setResult(RESULT_NEXT);
+ finish();
+ } else if(view == replyButton) {
+ Intent i = new Intent(this, WriteGroupMessageActivity.class);
+ i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
+ i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes());
+ startActivity(i);
+ setResult(RESULT_REPLY);
+ finish();
+ }
+ }
+}
diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
new file mode 100644
index 000000000..7a1a93c06
--- /dev/null
+++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
@@ -0,0 +1,217 @@
+package net.sf.briar.android.groups;
+
+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 java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+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.widgets.CommonLayoutParams;
+import net.sf.briar.android.widgets.HorizontalSpace;
+import net.sf.briar.api.android.BundleEncrypter;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DatabaseExecutor;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.messaging.Group;
+import net.sf.briar.api.messaging.GroupId;
+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 WriteGroupMessageActivity extends BriarActivity
+implements OnClickListener, OnItemSelectedListener {
+
+ private static final Logger LOG =
+ Logger.getLogger(WriteGroupMessageActivity.class.getName());
+
+ private final BriarServiceConnection serviceConnection =
+ new BriarServiceConnection();
+
+ @Inject private BundleEncrypter bundleEncrypter;
+ @Inject private DatabaseComponent db;
+ @Inject @DatabaseExecutor private Executor dbExecutor;
+ @Inject private MessageFactory messageFactory;
+
+ private Group group = null;
+ private GroupId groupId = null;
+ private MessageId parentId = null;
+ private GroupNameSpinnerAdapter adapter = null;
+ private Spinner spinner = null;
+ private ImageButton sendButton = null;
+ private EditText content = null;
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(null);
+
+ Intent i = getIntent();
+ byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
+ if(id != null) groupId = new GroupId(id);
+ id = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
+ if(id != null) parentId = new MessageId(id);
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+ layout.setOrientation(VERTICAL);
+
+ LinearLayout actionBar = new LinearLayout(this);
+ actionBar.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+ actionBar.setOrientation(HORIZONTAL);
+ actionBar.setGravity(CENTER_VERTICAL);
+
+ TextView to = new TextView(this);
+ to.setTextSize(18);
+ to.setPadding(10, 10, 10, 10);
+ to.setText(R.string.to);
+ actionBar.addView(to);
+
+ adapter = new GroupNameSpinnerAdapter(this);
+ spinner = new Spinner(this);
+ spinner.setAdapter(adapter);
+ spinner.setOnItemSelectedListener(this);
+ loadContactNames();
+ actionBar.addView(spinner);
+
+ actionBar.addView(new HorizontalSpace(this));
+
+ sendButton = new ImageButton(this);
+ sendButton.setBackgroundResource(0);
+ sendButton.setImageResource(R.drawable.social_send_now);
+ sendButton.setEnabled(false);
+ sendButton.setOnClickListener(this);
+ actionBar.addView(sendButton);
+ layout.addView(actionBar);
+
+ content = new EditText(this);
+ content.setPadding(10, 10, 10, 10);
+ 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 the DB to be opened
+ bindService(new Intent(BriarService.class.getName()),
+ serviceConnection, 0);
+ }
+
+ private void loadContactNames() {
+ final DatabaseComponent db = this.db;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ serviceConnection.waitForStartup();
+ final Collection groups = db.getSubscriptions();
+ runOnUiThread(new Runnable() {
+ public void run() {
+ for(Group g : groups) {
+ if(g.getId().equals(groupId)) {
+ group = g;
+ spinner.setSelection(adapter.getCount());
+ }
+ adapter.add(g);
+ }
+ }
+ });
+ } 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();
+ }
+ }
+ });
+ }
+
+ @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 onClick(View view) {
+ if(group == null) throw new IllegalStateException();
+ try {
+ storeMessage(content.getText().toString().getBytes("UTF-8"));
+ } catch(UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ finish();
+ }
+
+ private void storeMessage(final byte[] body) {
+ final DatabaseComponent db = this.db;
+ final MessageFactory messageFactory = this.messageFactory;
+ final Group group = this.group;
+ final MessageId parentId = this.parentId;
+ dbExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ serviceConnection.waitForStartup();
+ Message m = messageFactory.createAnonymousMessage(parentId,
+ group, "text/plain", body);
+ db.addLocalGroupMessage(m);
+ } 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);
+ }
+ }
+ });
+ }
+
+ public void onItemSelected(AdapterView> parent, View view, int position,
+ long id) {
+ group = adapter.getItem(position);
+ groupId = group.getId();
+ sendButton.setEnabled(true);
+ }
+
+ public void onNothingSelected(AdapterView> parent) {
+ group = null;
+ groupId = null;
+ sendButton.setEnabled(false);
+ }
+}
diff --git a/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java b/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java
index 90be870c0..9daf3287f 100644
--- a/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java
+++ b/briar-android/src/net/sf/briar/android/invitation/ConnectionView.java
@@ -4,7 +4,6 @@ import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import net.sf.briar.R;
import android.content.Context;
-import android.content.res.Resources;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -45,9 +44,9 @@ public class ConnectionView extends AddContactView {
innerLayout.addView(progress);
TextView connecting = new TextView(ctx);
- Resources res = getResources();
- String connectingVia = res.getString(R.string.connecting_wifi);
- connecting.setText(String.format(connectingVia, networkName));
+ String format = getResources().getString(
+ R.string.format_connecting_wifi);
+ connecting.setText(String.format(format, networkName));
innerLayout.addView(connecting);
addView(innerLayout);
diff --git a/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java b/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java
index 1305acc56..8cb63950f 100644
--- a/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java
+++ b/briar-android/src/net/sf/briar/android/invitation/WifiWidget.java
@@ -7,7 +7,6 @@ import net.sf.briar.R;
import net.sf.briar.android.widgets.CommonLayoutParams;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.view.View;
@@ -70,9 +69,9 @@ public class WifiWidget extends LinearLayout implements OnClickListener {
ok.setImageResource(R.drawable.navigation_accept);
ok.setPadding(10, 10, 10, 10);
addView(ok);
- Resources res = getResources();
- String connected = res.getString(R.string.wifi_connected);
- status.setText(String.format(connected, networkName));
+ String format = getResources().getString(
+ R.string.format_wifi_connected);
+ status.setText(String.format(format, networkName));
addView(status);
ImageButton settings = new ImageButton(ctx);
settings.setImageResource(R.drawable.action_settings);
diff --git a/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java b/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java
index 168d7aa81..18c4d00e7 100644
--- a/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java
+++ b/briar-android/src/net/sf/briar/android/messages/ContactNameSpinnerAdapter.java
@@ -22,6 +22,7 @@ implements SpinnerAdapter {
public View getView(int position, View convertView, ViewGroup parent) {
TextView name = new TextView(getContext());
name.setTextSize(18);
+ name.setMaxLines(1);
name.setPadding(10, 10, 10, 10);
name.setText(getItem(position).getName());
return name;
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
index 60d386901..b382e920e 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -67,6 +67,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
contactId = new ContactId(id);
contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
if(contactName == null) throw new IllegalStateException();
+ setTitle(contactName);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH);
@@ -104,23 +105,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
reloadMessageHeaders();
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- db.removeListener(this);
- unbindService(serviceConnection);
- }
-
- public void eventOccurred(DatabaseEvent e) {
- if(e instanceof MessageAddedEvent) {
- if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
- reloadMessageHeaders();
- } else if(e instanceof MessageExpiredEvent) {
- if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
- reloadMessageHeaders();
- }
- }
-
private void reloadMessageHeaders() {
final DatabaseComponent db = this.db;
final ContactId contactId = this.contactId;
@@ -168,8 +152,38 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
});
}
+ @Override
+ public void onActivityResult(int request, int result, Intent data) {
+ if(result == ReadPrivateMessageActivity.RESULT_PREV) {
+ int position = request - 1;
+ if(position >= 0 && position < adapter.getCount())
+ showMessage(position);
+ } else if(result == ReadPrivateMessageActivity.RESULT_NEXT) {
+ int position = request + 1;
+ if(position >= 0 && position < adapter.getCount())
+ showMessage(position);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ db.removeListener(this);
+ unbindService(serviceConnection);
+ }
+
+ public void eventOccurred(DatabaseEvent e) {
+ if(e instanceof MessageAddedEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
+ reloadMessageHeaders();
+ } else if(e instanceof MessageExpiredEvent) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
+ reloadMessageHeaders();
+ }
+ }
+
public void onClick(View view) {
- Intent i = new Intent(this, WriteMessageActivity.class);
+ Intent i = new Intent(this, WritePrivateMessageActivity.class);
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
startActivity(i);
}
@@ -181,28 +195,15 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
private void showMessage(int position) {
PrivateMessageHeader item = adapter.getItem(position);
- Intent i = new Intent(this, ReadMessageActivity.class);
+ Intent i = new Intent(this, ReadPrivateMessageActivity.class);
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
i.putExtra("net.sf.briar.CONTACT_NAME", contactName);
i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes());
i.putExtra("net.sf.briar.CONTENT_TYPE", item.getContentType());
i.putExtra("net.sf.briar.TIMESTAMP", item.getTimestamp());
+ i.putExtra("net.sf.briar.INCOMING", item.isIncoming());
i.putExtra("net.sf.briar.FIRST", position == 0);
i.putExtra("net.sf.briar.LAST", position == adapter.getCount() - 1);
- i.putExtra("net.sf.briar.STARRED", item.isStarred());
startActivityForResult(i, position);
}
-
- @Override
- public void onActivityResult(int request, int result, Intent data) {
- if(result == ReadMessageActivity.RESULT_PREV) {
- int position = request - 1;
- if(position >= 0 && position < adapter.getCount())
- showMessage(position);
- } else if(result == ReadMessageActivity.RESULT_NEXT) {
- int position = request + 1;
- if(position >= 0 && position < adapter.getCount())
- showMessage(position);
- }
- }
}
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
index 056935ad8..40e9626db 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
@@ -9,6 +9,7 @@ import java.util.ArrayList;
import net.sf.briar.R;
import net.sf.briar.android.widgets.CommonLayoutParams;
+import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.db.PrivateMessageHeader;
import android.content.Context;
import android.text.format.DateUtils;
@@ -34,23 +35,24 @@ class ConversationAdapter extends ArrayAdapter {
layout.setOrientation(HORIZONTAL);
layout.setGravity(CENTER_VERTICAL);
- if(!item.getContentType().equals("text/plain")) {
+ if(item.getContentType().equals("text/plain")) {
+ TextView subject = new TextView(ctx);
+ // Give me all the unused width
+ subject.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
+ subject.setTextSize(14);
+ subject.setMaxLines(2);
+ subject.setPadding(10, 10, 10, 10);
+ if(!item.isRead()) subject.setTypeface(null, BOLD);
+ subject.setText(item.getSubject());
+ layout.addView(subject);
+ } else {
ImageView attachment = new ImageView(ctx);
attachment.setPadding(10, 10, 10, 10);
attachment.setImageResource(R.drawable.content_attachment);
layout.addView(attachment);
+ layout.addView(new HorizontalSpace(ctx));
}
- TextView subject = new TextView(ctx);
- // Give me all the unused width
- subject.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
- subject.setTextSize(14);
- subject.setMaxLines(2);
- subject.setPadding(10, 10, 10, 10);
- if(!item.isRead()) subject.setTypeface(null, BOLD);
- subject.setText(item.getSubject());
- layout.addView(subject);
-
TextView date = new TextView(ctx);
date.setTextSize(14);
date.setPadding(0, 10, 10, 10);
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 561805748..5eafcf336 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -238,7 +238,7 @@ implements OnClickListener, DatabaseListener {
}
public void onClick(View view) {
- startActivity(new Intent(this, WriteMessageActivity.class));
+ startActivity(new Intent(this, WritePrivateMessageActivity.class));
}
public void eventOccurred(DatabaseEvent e) {
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
index 542a14d52..19066131b 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListAdapter.java
@@ -46,10 +46,12 @@ implements OnItemClickListener {
TextView name = new TextView(ctx);
name.setTextSize(18);
+ name.setMaxLines(1);
name.setPadding(10, 10, 10, 10);
int unread = item.getUnreadCount();
- if(unread > 0) name.setText(item.getName() + " (" + unread + ")");
- else name.setText(item.getName());
+ String contactName = item.getContactName();
+ if(unread > 0) name.setText(contactName + " (" + unread + ")");
+ else name.setText(contactName);
innerLayout.addView(name);
if(!StringUtils.isNullOrEmpty(item.getSubject())) {
@@ -78,7 +80,7 @@ implements OnItemClickListener {
ConversationListItem item = getItem(position);
Intent i = new Intent(getContext(), ConversationActivity.class);
i.putExtra("net.sf.briar.CONTACT_ID", item.getContactId().getInt());
- i.putExtra("net.sf.briar.CONTACT_NAME", item.getName());
+ i.putExtra("net.sf.briar.CONTACT_NAME", item.getContactName());
getContext().startActivity(i);
}
}
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
index b168f7a9b..eec30a91c 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java
@@ -30,7 +30,7 @@ class ConversationListItem {
return contact.getId();
}
- String getName() {
+ String getContactName() {
return contact.getName();
}
diff --git a/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
similarity index 90%
rename from briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java
rename to briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
index 04185e78f..afe094457 100644
--- a/briar-android/src/net/sf/briar/android/messages/ReadMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ReadPrivateMessageActivity.java
@@ -37,7 +37,7 @@ import android.widget.TextView;
import com.google.inject.Inject;
-public class ReadMessageActivity extends BriarActivity
+public class ReadPrivateMessageActivity extends BriarActivity
implements OnClickListener {
static final int RESULT_REPLY = RESULT_FIRST_USER;
@@ -45,7 +45,7 @@ implements OnClickListener {
static final int RESULT_NEXT = RESULT_FIRST_USER + 2;
private static final Logger LOG =
- Logger.getLogger(ReadMessageActivity.class.getName());
+ Logger.getLogger(ReadPrivateMessageActivity.class.getName());
private final BriarServiceConnection serviceConnection =
new BriarServiceConnection();
@@ -55,11 +55,9 @@ implements OnClickListener {
@Inject @DatabaseExecutor private Executor dbExecutor;
private ContactId contactId = null;
- private String contactName = null;
private MessageId messageId = null;
- private boolean first, last, starred, read;
- private ImageButton readButton = null;
- private ImageButton prevButton = null, nextButton = null;
+ private boolean read;
+ private ImageButton readButton = null, prevButton = null, nextButton = null;
private ImageButton replyButton = null;
private TextView content = null;
@@ -71,8 +69,9 @@ implements OnClickListener {
int cid = i.getIntExtra("net.sf.briar.CONTACT_ID", -1);
if(cid == -1) throw new IllegalStateException();
contactId = new ContactId(cid);
- contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
+ String contactName = i.getStringExtra("net.sf.briar.CONTACT_NAME");
if(contactName == null) throw new IllegalStateException();
+ setTitle(contactName);
byte[] mid = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
if(mid == null) throw new IllegalStateException();
messageId = new MessageId(mid);
@@ -80,14 +79,13 @@ implements OnClickListener {
if(contentType == null) throw new IllegalStateException();
long timestamp = i.getLongExtra("net.sf.briar.TIMESTAMP", -1);
if(timestamp == -1) throw new IllegalStateException();
- first = i.getBooleanExtra("net.sf.briar.FIRST", false);
- last = i.getBooleanExtra("net.sf.briar.LAST", false);
+ boolean incoming = i.getBooleanExtra("net.sf.briar.INCOMING", false);
+ boolean first = i.getBooleanExtra("net.sf.briar.FIRST", false);
+ boolean last = i.getBooleanExtra("net.sf.briar.LAST", false);
if(state != null && bundleEncrypter.decrypt(state)) {
- starred = state.getBoolean("net.sf.briar.STARRED");
read = state.getBoolean("net.sf.briar.READ");
} else {
- starred = i.getBooleanExtra("net.sf.briar.STARRED", false);
read = false;
setReadInDatabase(true);
}
@@ -112,8 +110,11 @@ implements OnClickListener {
// Give me all the unused width
name.setLayoutParams(CommonLayoutParams.WRAP_WRAP_1);
name.setTextSize(18);
+ name.setMaxLines(1);
name.setPadding(10, 10, 10, 10);
- String format = getResources().getString(R.string.message_from);
+ String format;
+ if(incoming) format = getResources().getString(R.string.format_from);
+ else format = getResources().getString(R.string.format_to);
name.setText(String.format(format, contactName));
header.addView(name);
@@ -204,7 +205,7 @@ implements OnClickListener {
private void setReadInUi(final boolean read) {
runOnUiThread(new Runnable() {
public void run() {
- ReadMessageActivity.this.read = read;
+ ReadPrivateMessageActivity.this.read = read;
if(read) readButton.setImageResource(R.drawable.content_unread);
else readButton.setImageResource(R.drawable.content_read);
}
@@ -241,7 +242,6 @@ implements OnClickListener {
@Override
public void onSaveInstanceState(Bundle state) {
- state.putBoolean("net.sf.briar.STARRED", starred);
state.putBoolean("net.sf.briar.READ", read);
bundleEncrypter.encrypt(state);
}
@@ -262,7 +262,7 @@ implements OnClickListener {
setResult(RESULT_NEXT);
finish();
} else if(view == replyButton) {
- Intent i = new Intent(this, WriteMessageActivity.class);
+ Intent i = new Intent(this, WritePrivateMessageActivity.class);
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
i.putExtra("net.sf.briar.PARENT_ID", messageId.getBytes());
startActivity(i);
diff --git a/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
similarity index 89%
rename from briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java
rename to briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
index 4673d8180..ced62326a 100644
--- a/briar-android/src/net/sf/briar/android/messages/WriteMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
@@ -7,6 +7,7 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -42,11 +43,11 @@ import android.widget.TextView;
import com.google.inject.Inject;
-public class WriteMessageActivity extends BriarActivity
+public class WritePrivateMessageActivity extends BriarActivity
implements OnClickListener, OnItemSelectedListener {
private static final Logger LOG =
- Logger.getLogger(WriteMessageActivity.class.getName());
+ Logger.getLogger(WritePrivateMessageActivity.class.getName());
private final BriarServiceConnection serviceConnection =
new BriarServiceConnection();
@@ -85,7 +86,7 @@ implements OnClickListener, OnItemSelectedListener {
TextView to = new TextView(this);
to.setTextSize(18);
to.setPadding(10, 10, 10, 10);
- to.setText(R.string.message_to);
+ to.setText(R.string.to);
actionBar.addView(to);
adapter = new ContactNameSpinnerAdapter(this);
@@ -163,32 +164,36 @@ implements OnClickListener, OnItemSelectedListener {
public void onClick(View view) {
if(contactId == null) throw new IllegalStateException();
try {
- byte[] body = content.getText().toString().getBytes("UTF-8");
- storeMessage(messageFactory.createPrivateMessage(parentId,
- "text/plain", body));
- } catch(IOException e) {
- throw new RuntimeException(e);
- } catch(GeneralSecurityException e) {
+ storeMessage(content.getText().toString().getBytes("UTF-8"));
+ } catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
finish();
}
- private void storeMessage(final Message m) {
+ private void storeMessage(final byte[] body) {
final DatabaseComponent db = this.db;
+ final MessageFactory messageFactory = this.messageFactory;
final ContactId contactId = this.contactId;
+ final MessageId parentId = this.parentId;
dbExecutor.execute(new Runnable() {
public void run() {
try {
serviceConnection.waitForStartup();
+ Message m = messageFactory.createPrivateMessage(parentId,
+ "text/plain", body);
db.addLocalPrivateMessage(m, contactId);
} 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);
}
}
});
diff --git a/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java b/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java
index 958092f40..c708f335c 100644
--- a/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java
+++ b/briar-android/src/net/sf/briar/android/widgets/HorizontalBorder.java
@@ -13,6 +13,6 @@ public class HorizontalBorder extends View {
public HorizontalBorder(Context ctx) {
super(ctx);
setLayoutParams(new LayoutParams(MATCH_PARENT, LINE_WIDTH));
- setBackgroundColor(getResources().getColor(R.color.HorizontalBorder));
+ setBackgroundColor(getResources().getColor(R.color.horizontal_border));
}
}