diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml
index c7cc015fe..90b7b6e69 100644
--- a/briar-android/res/values/color.xml
+++ b/briar-android/res/values/color.xml
@@ -3,6 +3,7 @@
#FFFFFF
#FFFFFF
#FFFFFF
+ #EEEEEE
#CCCCCC
#AAAAAA
#AAAAAA
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index b2d4ea85b..de9f61fd0 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -27,6 +27,7 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.MessageHeader;
import org.briarproject.api.db.NoSuchContactException;
+import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
@@ -34,6 +35,7 @@ import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageExpiredEvent;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.messaging.GroupId;
+import org.briarproject.api.messaging.MessageId;
import android.content.Intent;
import android.os.Bundle;
@@ -131,15 +133,11 @@ implements EventListener, OnClickListener, OnItemClickListener {
db.getInboxMessageHeaders(contactId);
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
- LOG.info("Load took " + duration + " ms");
+ LOG.info("Loading headers took " + duration + " ms");
displayHeaders(headers);
} catch(NoSuchContactException e) {
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
- runOnUiThread(new Runnable() {
- public void run() {
- finish();
- }
- });
+ finishOnUiThread();
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
@@ -163,21 +161,83 @@ implements EventListener, OnClickListener, OnItemClickListener {
adapter.add(new ConversationItem(h));
adapter.sort(ConversationItemComparator.INSTANCE);
adapter.notifyDataSetChanged();
- selectFirstUnread();
+ expandMessages();
}
});
}
- private void selectFirstUnread() {
- int firstUnread = -1, count = adapter.getCount();
+ private void expandMessages() {
+ // Expand unread messages and the last three messages
+ int firstExpanded = -1, count = adapter.getCount();
+ if(count == 0) return;
for(int i = 0; i < count; i++) {
- if(!adapter.getItem(i).getHeader().isRead()) {
- firstUnread = i;
- break;
+ ConversationItem item = adapter.getItem(i);
+ if(!item.getHeader().isRead() || i >= count - 3) {
+ if(firstExpanded == -1) firstExpanded = i;
+ item.setExpanded(true);
+ loadMessage(item.getHeader());
}
}
- if(firstUnread == -1) list.setSelection(count - 1);
- else list.setSelection(firstUnread);
+ // Scroll to the first expanded message
+ if(firstExpanded == -1) list.setSelection(count - 1);
+ else list.setSelection(firstExpanded);
+ }
+
+ private void loadMessage(final MessageHeader h) {
+ dbUiExecutor.execute(new Runnable() {
+ public void run() {
+ try {
+ lifecycleManager.waitForDatabase();
+ long now = System.currentTimeMillis();
+ byte[] body = db.getMessageBody(h.getId());
+ long duration = System.currentTimeMillis() - now;
+ if(LOG.isLoggable(INFO))
+ LOG.info("Loading message took " + duration + " ms");
+ displayMessage(h.getId(), body);
+ if(!h.isRead()) {
+ now = System.currentTimeMillis();
+ db.setReadFlag(h.getId(), true);
+ duration = System.currentTimeMillis() - now;
+ if(LOG.isLoggable(INFO))
+ LOG.info("Setting read took " + duration + " ms");
+ }
+ } catch(NoSuchMessageException e) {
+ if(LOG.isLoggable(INFO)) LOG.info("Message expired");
+ // The item will be removed when we get the event
+ } 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 database");
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+ }
+
+ private void displayMessage(final MessageId m, final byte[] body) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ int count = adapter.getCount();
+ for(int i = 0; i < count; i++) {
+ ConversationItem item = adapter.getItem(i);
+ if(item.getHeader().getId().equals(m)) {
+ item.setBody(body);
+ adapter.notifyDataSetChanged();
+ return;
+ }
+ }
+ }
+ });
+ }
+
+ private void finishOnUiThread() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ finish();
+ }
+ });
}
@Override
@@ -201,11 +261,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if(c.getContactId().equals(contactId)) {
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
- runOnUiThread(new Runnable() {
- public void run() {
- finish();
- }
- });
+ finishOnUiThread();
}
} else if(e instanceof MessageAddedEvent) {
GroupId g = ((MessageAddedEvent) e).getGroup().getId();
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index f22216ec7..62f4eebc5 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -1,7 +1,9 @@
package org.briarproject.android.contact;
+import static android.view.Gravity.CENTER_HORIZONTAL;
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 org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
import static org.briarproject.api.Author.Status.VERIFIED;
@@ -12,6 +14,7 @@ import org.briarproject.R;
import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.db.MessageHeader;
+import org.briarproject.util.StringUtils;
import android.content.Context;
import android.content.res.Resources;
@@ -20,6 +23,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
+import android.widget.ProgressBar;
import android.widget.TextView;
class ConversationAdapter extends ArrayAdapter {
@@ -34,29 +38,53 @@ class ConversationAdapter extends ArrayAdapter {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- MessageHeader header = getItem(position).getHeader();
+ ConversationItem item = getItem(position);
+ MessageHeader header = item.getHeader();
Context ctx = getContext();
+ Resources res = ctx.getResources();
- LinearLayout layout = new LinearLayout(ctx);
- layout.setOrientation(HORIZONTAL);
- layout.setGravity(CENTER_VERTICAL);
- if(!header.isRead()) {
- Resources res = ctx.getResources();
- layout.setBackgroundColor(res.getColor(R.color.unread_background));
- }
+ LinearLayout headerLayout = new LinearLayout(ctx);
+ headerLayout.setOrientation(HORIZONTAL);
+ headerLayout.setGravity(CENTER_VERTICAL);
+ int background;
+ if(header.isRead()) background = res.getColor(R.color.read_background);
+ else background = res.getColor(R.color.unread_background);
+ headerLayout.setBackgroundColor(background);
AuthorView authorView = new AuthorView(ctx);
authorView.setLayoutParams(WRAP_WRAP_1);
authorView.init(header.getAuthor().getName(), VERIFIED);
- layout.addView(authorView);
+ headerLayout.addView(authorView);
TextView date = new TextView(ctx);
date.setTextSize(14);
date.setPadding(0, pad, pad, pad);
long then = header.getTimestamp(), now = System.currentTimeMillis();
date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
- layout.addView(date);
+ headerLayout.addView(date);
- return layout;
+ if(!item.isExpanded()) return headerLayout;
+
+ LinearLayout expanded = new LinearLayout(ctx);
+ expanded.setOrientation(VERTICAL);
+ expanded.setGravity(CENTER_HORIZONTAL);
+ expanded.setBackgroundColor(background);
+ expanded.addView(headerLayout);
+
+ byte[] body = item.getBody();
+ if(body == null) {
+ ProgressBar progress = new ProgressBar(ctx);
+ progress.setPadding(pad, 0, pad, pad);
+ progress.setIndeterminate(true);
+ expanded.addView(progress);
+ } else if(header.getContentType().equals("text/plain")) {
+ TextView text = new TextView(ctx);
+ text.setPadding(pad, 0, pad, pad);
+ text.setBackgroundColor(background);
+ text.setText(StringUtils.fromUtf8(body));
+ expanded.addView(text);
+ }
+
+ return expanded;
}
-}
+}
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java b/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java
index 9cb224fcf..717c682d6 100644
--- a/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ReadPrivateMessageActivity.java
@@ -12,7 +12,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
import static org.briarproject.api.Author.Status.VERIFIED;
-import java.io.UnsupportedEncodingException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -32,6 +31,7 @@ import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.messaging.GroupId;
import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
import android.content.Intent;
import android.content.res.Resources;
@@ -227,7 +227,7 @@ implements OnClickListener {
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Loading message took " + duration + " ms");
- final String text = new String(body, "UTF-8");
+ final String text = StringUtils.fromUtf8(body);
runOnUiThread(new Runnable() {
public void run() {
content.setText(text);
@@ -247,8 +247,6 @@ implements OnClickListener {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for database");
Thread.currentThread().interrupt();
- } catch(UnsupportedEncodingException e) {
- throw new RuntimeException(e);
}
}
});
diff --git a/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java b/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java
index 743cc6d31..4f44edd91 100644
--- a/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/WritePrivateMessageActivity.java
@@ -14,7 +14,6 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -39,6 +38,7 @@ import org.briarproject.api.messaging.GroupId;
import org.briarproject.api.messaging.Message;
import org.briarproject.api.messaging.MessageFactory;
import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
import android.content.Intent;
import android.content.res.Resources;
@@ -194,11 +194,7 @@ implements OnClickListener {
public void onClick(View view) {
if(localAuthor == null) throw new IllegalStateException();
- try {
- createMessage(content.getText().toString().getBytes("UTF-8"));
- } catch(UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
+ createMessage(StringUtils.toUtf8(content.getText().toString()));
Toast.makeText(this, R.string.message_sent_toast, LENGTH_LONG).show();
finish();
}
diff --git a/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java b/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java
index ac56f116b..132663dc7 100644
--- a/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/CreateGroupActivity.java
@@ -14,7 +14,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
import static org.briarproject.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
-import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executor;
@@ -35,6 +34,7 @@ import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.messaging.Group;
import org.briarproject.api.messaging.GroupFactory;
+import org.briarproject.util.StringUtils;
import android.content.Intent;
import android.os.Bundle;
@@ -245,12 +245,8 @@ SelectContactsDialog.Listener {
private boolean validateName() {
if(nameEntry.getText().length() == 0) return false;
- try {
- byte[] b = nameEntry.getText().toString().getBytes("UTF-8");
- if(b.length > MAX_GROUP_NAME_LENGTH) return false;
- } catch(UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
+ byte[] b = StringUtils.toUtf8(nameEntry.getText().toString());
+ if(b.length > MAX_GROUP_NAME_LENGTH) return false;
// Hide the soft keyboard
Object o = getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
diff --git a/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java b/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java
index 4ce3bba7c..41480c2bc 100644
--- a/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/ReadGroupPostActivity.java
@@ -11,7 +11,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
-import java.io.UnsupportedEncodingException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -31,6 +30,7 @@ import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.messaging.GroupId;
import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
import android.content.Intent;
import android.content.res.Resources;
@@ -223,7 +223,7 @@ implements OnClickListener {
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Loading message took " + duration + " ms");
- final String text = new String(body, "UTF-8");
+ final String text = StringUtils.fromUtf8(body);
runOnUiThread(new Runnable() {
public void run() {
content.setText(text);
@@ -243,8 +243,6 @@ implements OnClickListener {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for database");
Thread.currentThread().interrupt();
- } catch(UnsupportedEncodingException e) {
- throw new RuntimeException(e);
}
}
});
diff --git a/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java b/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java
index badcb0587..071195d23 100644
--- a/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java
+++ b/briar-android/src/org/briarproject/android/groups/WriteGroupPostActivity.java
@@ -15,7 +15,6 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -46,6 +45,7 @@ import org.briarproject.api.messaging.GroupId;
import org.briarproject.api.messaging.Message;
import org.briarproject.api.messaging.MessageFactory;
import org.briarproject.api.messaging.MessageId;
+import org.briarproject.util.StringUtils;
import android.content.Intent;
import android.content.res.Resources;
@@ -269,11 +269,7 @@ implements OnItemSelectedListener, OnClickListener {
public void onClick(View view) {
if(group == null) throw new IllegalStateException();
- try {
- createMessage(content.getText().toString().getBytes("UTF-8"));
- } catch(UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
+ createMessage(StringUtils.toUtf8(content.getText().toString()));
Toast.makeText(this, R.string.post_sent_toast, LENGTH_LONG).show();
finish();
}
diff --git a/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java b/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java
index 9a5b83211..815e5e1fb 100644
--- a/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java
+++ b/briar-android/src/org/briarproject/android/identity/CreateIdentityActivity.java
@@ -14,7 +14,6 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import java.io.UnsupportedEncodingException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -32,6 +31,7 @@ import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.util.StringUtils;
import android.content.Intent;
import android.os.Bundle;
@@ -138,12 +138,8 @@ implements OnEditorActionListener, OnClickListener {
private boolean validateNickname() {
if(nicknameEntry.getText().length() == 0) return false;
- try {
- byte[] b = nicknameEntry.getText().toString().getBytes("UTF-8");
- if(b.length > MAX_AUTHOR_NAME_LENGTH) return false;
- } catch(UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
+ byte[] b = StringUtils.toUtf8(nicknameEntry.getText().toString());
+ if(b.length > MAX_AUTHOR_NAME_LENGTH) return false;
// Hide the soft keyboard
Object o = getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);