Android UI for group messages (anonymous text only, no moderation yet).

This commit is contained in:
akwizgran
2013-03-12 15:55:41 +00:00
parent 211950ce66
commit 747a06d1ad
32 changed files with 1438 additions and 103 deletions

View File

@@ -40,6 +40,22 @@
android:name=".android.contact.ContactListActivity"
android:label="@string/contact_list_title" >
</activity>
<activity
android:name=".android.groups.GroupActivity"
android:label="@string/groups_title" >
</activity>
<activity
android:name=".android.groups.GroupListActivity"
android:label="@string/groups_title" >
</activity>
<activity
android:name=".android.groups.ReadGroupMessageActivity"
android:label="@string/groups_title" >
</activity>
<activity
android:name=".android.groups.WriteGroupMessageActivity"
android:label="@string/compose_group_title" >
</activity>
<activity
android:name=".android.invitation.AddContactActivity"
android:label="@string/add_contact_title" >
@@ -53,12 +69,12 @@
android:label="@string/messages_title" >
</activity>
<activity
android:name=".android.messages.ReadMessageActivity"
android:name="net.sf.briar.android.messages.ReadPrivateMessageActivity"
android:label="@string/messages_title" >
</activity>
<activity
android:name=".android.messages.WriteMessageActivity"
android:label="@string/compose_title" >
android:name="net.sf.briar.android.messages.WritePrivateMessageActivity"
android:label="@string/compose_message_title" >
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="HorizontalBorder">#CCCCCC</color>
<color name="horizontal_border">#CCCCCC</color>
<color name="anonymous_author">#999999</color>
<color name="pseudonymous_author">#000000</color>
</resources>

View File

@@ -11,14 +11,13 @@
<string name="quit_button">Quit</string>
<string name="contact_list_title">Contacts</string>
<string name="contact_connected">Connected</string>
<string name="contact_last_connected">Last connected &lt;br /&gt; %1$s</string>
<string name="search_button">Search</string>
<string name="format_contact_last_connected">Last connected &lt;br /&gt; %1$s</string>
<string name="add_contact_title">Add a Contact</string>
<string name="same_network">Briar can add contacts via Wi-Fi or Bluetooth. To use Wi-Fi you must both be connected to the same network.</string>
<string name="wifi_not_available">Wi-Fi is not available on this device</string>
<string name="wifi_disabled">Wi-Fi is OFF</string>
<string name="wifi_disconnected">Wi-Fi is DISCONNECTED</string>
<string name="wifi_connected">Wi-Fi is CONNECTED to %1$s</string>
<string name="format_wifi_connected">Wi-Fi is CONNECTED to %1$s</string>
<string name="bluetooth_not_available">Bluetooth is not available on this device</string>
<string name="bluetooth_disabled">Bluetooth is OFF</string>
<string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE</string>
@@ -26,7 +25,7 @@
<string name="continue_button">Continue</string>
<string name="your_invitation_code">Your invitation code is</string>
<string name="enter_invitation_code">Please enter your contact\'s invitation code:</string>
<string name="connecting_wifi">Connecting via %1$s\u2026</string>
<string name="format_connecting_wifi">Connecting via %1$s\u2026</string>
<string name="connecting_bluetooth">Connecting via Bluetooth\u2026</string>
<string name="connection_failed">Connection failed</string>
<string name="check_same_network">Please check that you are both using the same network.</string>
@@ -41,7 +40,11 @@
<string name="enter_nickname">Please enter a nickname for this contact:</string>
<string name="done_button">Done</string>
<string name="messages_title">Messages</string>
<string name="message_from">From: %1$s</string>
<string name="compose_title">New Message</string>
<string name="message_to">To:</string>
<string name="format_from">From: %1$s</string>
<string name="format_to">To: %1$s</string>
<string name="compose_message_title">New Message</string>
<string name="to">To:</string>
<string name="groups_title">Groups</string>
<string name="anonymous">(Anonymous)</string>
<string name="compose_group_title">New Post</string>
</resources>

View File

@@ -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);

View File

@@ -7,7 +7,7 @@ class ContactComparator implements Comparator<ContactListItem> {
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());
}
}

View File

@@ -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)));

View File

@@ -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();
}

View File

@@ -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<GroupMessageHeader> 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<GroupMessageHeader> headers) {
runOnUiThread(new Runnable() {
public void run() {
List<GroupMessageHeader> sort =
new ArrayList<GroupMessageHeader>(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);
}
}
}

View File

@@ -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<GroupMessageHeader> {
GroupAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<GroupMessageHeader>());
}
@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;
}
}

View File

@@ -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<Group> 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<Group> groups = db.getSubscriptions();
if(LOG.isLoggable(INFO))
LOG.info("Loaded " + groups.size() + " groups");
List<GroupListItem> items = new ArrayList<GroupListItem>();
for(Group g : groups) {
// Filter out restricted groups
if(g.getPublicKey() != null) continue;
Collection<GroupMessageHeader> 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<GroupMessageHeader> headers) {
List<GroupMessageHeader> sort =
new ArrayList<GroupMessageHeader>(headers);
Collections.sort(sort, DescendingHeaderComparator.INSTANCE);
return new GroupListItem(group, sort);
}
private void updateGroupList(final Collection<GroupListItem> 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<GroupListItem> {
private static final GroupComparator INSTANCE = new GroupComparator();
public int compare(GroupListItem a, GroupListItem b) {
return String.CASE_INSENSITIVE_ORDER.compare(a.getGroupName(),
b.getGroupName());
}
}
}

View File

@@ -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<GroupListItem>
implements OnItemClickListener {
GroupListAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<GroupListItem>());
}
@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);
}
}

View File

@@ -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<GroupMessageHeader> 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;
}
}

View File

@@ -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<Group>
implements SpinnerAdapter {
GroupNameSpinnerAdapter(Context context) {
super(context, android.R.layout.simple_spinner_item,
new ArrayList<Group>());
}
@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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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<Group> 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);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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<PrivateMessageHeader> {
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);

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -30,7 +30,7 @@ class ConversationListItem {
return contact.getId();
}
String getName() {
String getContactName() {
return contact.getName();
}

View File

@@ -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);

View File

@@ -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);
}
}
});

View File

@@ -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));
}
}