diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml index c7cc015fe1b363703b9d5a35ac101718e30b519f..90b7b6e69693a473d941c46649d930d570b0dd62 100644 --- a/briar-android/res/values/color.xml +++ b/briar-android/res/values/color.xml @@ -3,6 +3,7 @@ <color name="home_screen_background">#FFFFFF</color> <color name="content_background">#FFFFFF</color> <color name="unread_background">#FFFFFF</color> + <color name="read_background">#EEEEEE</color> <color name="horizontal_border">#CCCCCC</color> <color name="no_posts">#AAAAAA</color> <color name="no_messages">#AAAAAA</color> diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index b2d4ea85b5e5c4ff4a2573e95b8ae811a6c4b614..de9f61fd06a6cacc0e38d29cc3e5028893e0a118 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 f22216ec71a10f81d4a8c53eddf2767cd2c8094c..62f4eebc5383979180ecbae425e8dba7b82b3501 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<ConversationItem> { @@ -34,29 +38,53 @@ class ConversationAdapter extends ArrayAdapter<ConversationItem> { @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); + + 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 layout; + 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 9cb224fcf5a7d9eceb1d28492e73ab9dc419f05d..717c682d63e76b3b0fcee5320f79281dd36ec220 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 743cc6d31c34c0f8268ac026d9487166950ef072..4f44edd91899dc9ca1948363d943b658aeb89337 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 ac56f116b5db0317c6d51a99109f54aaaeed5d7b..132663dc78e1cd867b385eb19ed2422c9199e8fd 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 4ce3bba7c5205255eaecedd9563062f1647e23a3..41480c2bc43326e22a8c6c8d11a0aea3691c739c 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 badcb0587c2caf157184465fd8a035d4fa155dfe..071195d23f86e6fef887acaf9a2e6f85c4f93713 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 9a5b8321145ab4eef42870fcf0e019f58ae40308..815e5e1fb232ae0b3e6cd2ad1454d4e52cf1d1e0 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);