Show whether identities are anonymous, unknown, or verified.

Dev task #52. Known but unverified identities are also supported, but
currently unused. These will be used in future for contacts who've been
introduced but not verified face to face.
This commit is contained in:
akwizgran
2014-02-04 12:32:51 +00:00
parent 035fc4324f
commit a45d09ef5c
29 changed files with 197 additions and 95 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 B

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -4,7 +4,6 @@
<color name="content_background">#FFFFFF</color> <color name="content_background">#FFFFFF</color>
<color name="unread_background">#FFFFFF</color> <color name="unread_background">#FFFFFF</color>
<color name="horizontal_border">#CCCCCC</color> <color name="horizontal_border">#CCCCCC</color>
<color name="anonymous_author">#AAAAAA</color>
<color name="no_posts">#AAAAAA</color> <color name="no_posts">#AAAAAA</color>
<color name="no_messages">#AAAAAA</color> <color name="no_messages">#AAAAAA</color>
</resources> </resources>

View File

@@ -3,10 +3,12 @@ package org.briarproject.android.contact;
import static android.widget.LinearLayout.HORIZONTAL; import static android.widget.LinearLayout.HORIZONTAL;
import static java.text.DateFormat.SHORT; import static java.text.DateFormat.SHORT;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1; import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
import static org.briarproject.api.Author.Status.VERIFIED;
import java.util.ArrayList; import java.util.ArrayList;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.LayoutUtils; import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.db.MessageHeader; import org.briarproject.api.db.MessageHeader;
@@ -41,14 +43,10 @@ class ConversationAdapter extends ArrayAdapter<ConversationItem> {
layout.setBackgroundColor(res.getColor(R.color.unread_background)); layout.setBackgroundColor(res.getColor(R.color.unread_background));
} }
TextView name = new TextView(ctx); AuthorView authorView = new AuthorView(ctx);
// Give me all the unused width authorView.setLayoutParams(WRAP_WRAP_1);
name.setLayoutParams(WRAP_WRAP_1); authorView.init(header.getAuthor().getName(), VERIFIED);
name.setTextSize(18); layout.addView(authorView);
name.setMaxLines(1);
name.setPadding(pad, pad, pad, pad);
name.setText(header.getAuthor().getName());
layout.addView(name);
TextView date = new TextView(ctx); TextView date = new TextView(ctx);
date.setTextSize(14); date.setTextSize(14);

View File

@@ -10,6 +10,7 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP; 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.MATCH_WRAP_1;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_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.io.UnsupportedEncodingException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -18,8 +19,9 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.util.HorizontalBorder; import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.ElasticHorizontalSpace; import org.briarproject.android.util.ElasticHorizontalSpace;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.LayoutUtils; import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.AuthorId; import org.briarproject.api.AuthorId;
import org.briarproject.api.android.DatabaseUiExecutor; import org.briarproject.api.android.DatabaseUiExecutor;
@@ -116,16 +118,12 @@ implements OnClickListener {
header.setOrientation(HORIZONTAL); header.setOrientation(HORIZONTAL);
header.setGravity(CENTER_VERTICAL); header.setGravity(CENTER_VERTICAL);
int pad = LayoutUtils.getPadding(this); AuthorView author = new AuthorView(this);
author.setLayoutParams(WRAP_WRAP_1);
author.init(authorName, VERIFIED);
header.addView(author);
TextView name = new TextView(this); int pad = LayoutUtils.getPadding(this);
// Give me all the unused width
name.setLayoutParams(WRAP_WRAP_1);
name.setTextSize(18);
name.setMaxLines(1);
name.setPadding(pad, pad, pad, pad);
name.setText(authorName);
header.addView(name);
TextView date = new TextView(this); TextView date = new TextView(this);
date.setTextSize(14); date.setTextSize(14);

View File

@@ -230,6 +230,7 @@ OnClickListener, OnItemClickListener {
i.putExtra("briar.AUTHOR_ID", author.getId().getBytes()); i.putExtra("briar.AUTHOR_ID", author.getId().getBytes());
i.putExtra("briar.AUTHOR_NAME", author.getName()); i.putExtra("briar.AUTHOR_NAME", author.getName());
} }
i.putExtra("briar.AUTHOR_STATUS", item.getAuthorStatus().name());
i.putExtra("briar.CONTENT_TYPE", item.getContentType()); i.putExtra("briar.CONTENT_TYPE", item.getContentType());
i.putExtra("briar.TIMESTAMP", item.getTimestamp()); i.putExtra("briar.TIMESTAMP", item.getTimestamp());
startActivityForResult(i, position); startActivityForResult(i, position);

View File

@@ -7,6 +7,7 @@ import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
import java.util.ArrayList; import java.util.ArrayList;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.LayoutUtils; import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.Author; import org.briarproject.api.Author;
import org.briarproject.api.db.MessageHeader; import org.briarproject.api.db.MessageHeader;
@@ -32,34 +33,27 @@ class GroupAdapter extends ArrayAdapter<MessageHeader> {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
MessageHeader item = getItem(position); MessageHeader header = getItem(position);
Context ctx = getContext(); Context ctx = getContext();
Resources res = ctx.getResources();
LinearLayout layout = new LinearLayout(ctx); LinearLayout layout = new LinearLayout(ctx);
layout.setOrientation(HORIZONTAL); layout.setOrientation(HORIZONTAL);
if(!item.isRead()) if(!header.isRead()) {
Resources res = ctx.getResources();
layout.setBackgroundColor(res.getColor(R.color.unread_background)); layout.setBackgroundColor(res.getColor(R.color.unread_background));
TextView name = new TextView(ctx);
// Give me all the unused width
name.setLayoutParams(WRAP_WRAP_1);
name.setTextSize(18);
name.setMaxLines(1);
name.setPadding(pad, pad, pad, pad);
Author author = item.getAuthor();
if(author == null) {
name.setTextColor(res.getColor(R.color.anonymous_author));
name.setText(R.string.anonymous);
} else {
name.setText(author.getName());
} }
layout.addView(name);
AuthorView authorView = new AuthorView(ctx);
authorView.setLayoutParams(WRAP_WRAP_1);
Author author = header.getAuthor();
if(author == null) authorView.init(null, header.getAuthorStatus());
else authorView.init(author.getName(), header.getAuthorStatus());
layout.addView(authorView);
TextView date = new TextView(ctx); TextView date = new TextView(ctx);
date.setTextSize(14); date.setTextSize(14);
date.setPadding(pad, pad, pad, pad); date.setPadding(0, pad, pad, pad);
long then = item.getTimestamp(), now = System.currentTimeMillis(); long then = header.getTimestamp(), now = System.currentTimeMillis();
date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT)); date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
layout.addView(date); layout.addView(date);

View File

@@ -18,9 +18,11 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.util.HorizontalBorder; import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.ElasticHorizontalSpace; import org.briarproject.android.util.ElasticHorizontalSpace;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.LayoutUtils; import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.Author;
import org.briarproject.api.android.DatabaseUiExecutor; import org.briarproject.api.android.DatabaseUiExecutor;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
@@ -83,6 +85,9 @@ implements OnClickListener {
timestamp = i.getLongExtra("briar.TIMESTAMP", -1); timestamp = i.getLongExtra("briar.TIMESTAMP", -1);
if(timestamp == -1) throw new IllegalStateException(); if(timestamp == -1) throw new IllegalStateException();
String authorName = i.getStringExtra("briar.AUTHOR_NAME"); String authorName = i.getStringExtra("briar.AUTHOR_NAME");
String s = i.getStringExtra("briar.AUTHOR_STATUS");
if(s == null) throw new IllegalStateException();
Author.Status authorStatus = Author.Status.valueOf(s);
if(state == null) { if(state == null) {
read = false; read = false;
@@ -109,21 +114,12 @@ implements OnClickListener {
header.setOrientation(HORIZONTAL); header.setOrientation(HORIZONTAL);
header.setGravity(CENTER_VERTICAL); header.setGravity(CENTER_VERTICAL);
int pad = LayoutUtils.getPadding(this); AuthorView author = new AuthorView(this);
author.setLayoutParams(WRAP_WRAP_1);
author.init(authorName, authorStatus);
header.addView(author);
TextView name = new TextView(this); int pad = LayoutUtils.getPadding(this);
// Give me all the unused width
name.setLayoutParams(WRAP_WRAP_1);
name.setTextSize(18);
name.setMaxLines(1);
name.setPadding(pad, pad, pad, pad);
if(authorName == null) {
name.setTextColor(res.getColor(R.color.anonymous_author));
name.setText(R.string.anonymous);
} else {
name.setText(authorName);
}
header.addView(name);
TextView date = new TextView(this); TextView date = new TextView(this);
date.setTextSize(14); date.setTextSize(14);

View File

@@ -0,0 +1,51 @@
package org.briarproject.android.util;
import org.briarproject.R;
import org.briarproject.api.Author;
import android.content.Context;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class AuthorView extends LinearLayout {
public AuthorView(Context ctx) {
super(ctx);
}
public void init(String name, Author.Status status) {
Context ctx = getContext();
int pad = LayoutUtils.getPadding(ctx);
setOrientation(VERTICAL);
TextView nameView = new TextView(ctx);
// Give me all the unused width
nameView.setTextSize(18);
nameView.setMaxLines(1);
nameView.setPadding(pad, pad, pad, pad);
if(name == null) nameView.setText(R.string.anonymous);
else nameView.setText(name);
addView(nameView);
LinearLayout statusLayout = new LinearLayout(ctx);
statusLayout.setOrientation(HORIZONTAL);
ImageView statusView = new ImageView(ctx);
statusView.setPadding(pad, 0, pad, pad);
switch(status) {
case ANONYMOUS:
statusView.setImageResource(R.drawable.identity_anonymous);
break;
case UNKNOWN:
statusView.setImageResource(R.drawable.identity_unknown);
break;
case UNVERIFIED:
statusView.setImageResource(R.drawable.identity_unverified);
break;
case VERIFIED:
statusView.setImageResource(R.drawable.identity_verified);
break;
}
statusLayout.addView(statusView);
statusLayout.addView(new ElasticHorizontalSpace(ctx));
addView(statusLayout);
}
}

View File

@@ -7,6 +7,8 @@ import java.io.UnsupportedEncodingException;
/** A pseudonym for a user. */ /** A pseudonym for a user. */
public class Author { public class Author {
public enum Status { ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED };
private final AuthorId id; private final AuthorId id;
private final String name; private final String name;
private final byte[] publicKey; private final byte[] publicKey;

View File

@@ -9,16 +9,19 @@ public class MessageHeader {
private final MessageId id, parent; private final MessageId id, parent;
private final GroupId groupId; private final GroupId groupId;
private final Author author; private final Author author;
private final Author.Status authorStatus;
private final String contentType; private final String contentType;
private final long timestamp; private final long timestamp;
private final boolean read; private final boolean read;
public MessageHeader(MessageId id, MessageId parent, GroupId groupId, public MessageHeader(MessageId id, MessageId parent, GroupId groupId,
Author author, String contentType, long timestamp, boolean read) { Author author, Author.Status authorStatus, String contentType,
long timestamp, boolean read) {
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
this.groupId = groupId; this.groupId = groupId;
this.author = author; this.author = author;
this.authorStatus = authorStatus;
this.contentType = contentType; this.contentType = contentType;
this.timestamp = timestamp; this.timestamp = timestamp;
this.read = read; this.read = read;
@@ -51,6 +54,11 @@ public class MessageHeader {
return author; return author;
} }
/** Returns the status of the message's author. */
public Author.Status getAuthorStatus() {
return authorStatus;
}
/** Returns the message's content type. */ /** Returns the message's content type. */
public String getContentType() { public String getContentType() {
return contentType; return contentType;

View File

@@ -4,6 +4,9 @@ import static java.sql.Types.BINARY;
import static java.sql.Types.VARCHAR; import static java.sql.Types.VARCHAR;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.Author.Status.ANONYMOUS;
import static org.briarproject.api.Author.Status.UNKNOWN;
import static org.briarproject.api.Author.Status.VERIFIED;
import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS; import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
import static org.briarproject.api.messaging.MessagingConstants.RETENTION_GRANULARITY; import static org.briarproject.api.messaging.MessagingConstants.RETENTION_GRANULARITY;
import static org.briarproject.db.ExponentialBackoff.calculateExpiry; import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
@@ -1525,10 +1528,12 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean read = rs.getBoolean(7); boolean read = rs.getBoolean(7);
if(incoming) { if(incoming) {
headers.add(new MessageHeader(id, parent, groupId, headers.add(new MessageHeader(id, parent, groupId,
remoteAuthor, contentType, timestamp, read)); remoteAuthor, VERIFIED, contentType, timestamp,
read));
} else { } else {
headers.add(new MessageHeader(id, parent, groupId, headers.add(new MessageHeader(id, parent, groupId,
localAuthor, contentType, timestamp, read)); localAuthor, VERIFIED, contentType, timestamp,
read));
} }
} }
rs.close(); rs.close();
@@ -1701,9 +1706,14 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT messageId, parentId, authorId, authorName," String sql = "SELECT messageId, parentId, m.authorId, authorName,"
+ " authorKey, contentType, timestamp, read" + " authorKey, contentType, timestamp, read,"
+ " FROM messages" + " la.authorId IS NOT NULL, c.authorId IS NOT NULL"
+ " FROM messages AS m"
+ " LEFT OUTER JOIN localAuthors AS la"
+ " ON m.authorId = la.authorId"
+ " LEFT OUTER JOIN contacts AS c"
+ " ON m.authorId = c.authorId"
+ " WHERE groupId = ?"; + " WHERE groupId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
@@ -1726,8 +1736,14 @@ abstract class JdbcDatabase implements Database<Connection> {
String contentType = rs.getString(6); String contentType = rs.getString(6);
long timestamp = rs.getLong(7); long timestamp = rs.getLong(7);
boolean read = rs.getBoolean(8); boolean read = rs.getBoolean(8);
boolean isSelf = rs.getBoolean(9);
boolean isContact = rs.getBoolean(10);
Author.Status authorStatus;
if(author == null) authorStatus = ANONYMOUS;
else if(isSelf || isContact) authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
headers.add(new MessageHeader(id, parent, g, author, headers.add(new MessageHeader(id, parent, g, author,
contentType, timestamp, read)); authorStatus, contentType, timestamp, read));
} }
rs.close(); rs.close();
ps.close(); ps.close();

View File

@@ -41,7 +41,6 @@ import org.briarproject.api.messaging.MessageId;
import org.briarproject.api.transport.Endpoint; import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.TemporarySecret; import org.briarproject.api.transport.TemporarySecret;
import org.briarproject.system.SystemClock; import org.briarproject.system.SystemClock;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -898,42 +897,26 @@ public class H2DatabaseTest extends BriarTestCase {
// Mark one of the messages read // Mark one of the messages read
db.setReadFlag(txn, messageId, true); db.setReadFlag(txn, messageId, true);
// Retrieve the message headers // Retrieve the message headers (order is undefined)
Collection<MessageHeader> headers = db.getMessageHeaders(txn, groupId); Collection<MessageHeader> headers = db.getMessageHeaders(txn, groupId);
Iterator<MessageHeader> it = headers.iterator(); assertEquals(2, headers.size());
boolean messageFound = false, message1Found = false; boolean firstFound = false, secondFound = false;
// First header (order is undefined) for(MessageHeader header : headers) {
assertTrue(it.hasNext()); if(messageId.equals(header.getId())) {
MessageHeader header = it.next(); assertHeadersMatch(message, header);
if(messageId.equals(header.getId())) { assertTrue(header.isRead());
assertHeadersMatch(message, header); firstFound = true;
assertTrue(header.isRead()); } else if(messageId1.equals(header.getId())) {
messageFound = true; assertHeadersMatch(message1, header);
} else if(messageId1.equals(header.getId())) { assertFalse(header.isRead());
assertHeadersMatch(message1, header); secondFound = true;
assertFalse(header.isRead()); } else {
message1Found = true; fail();
} else { }
fail();
} }
// Second header // Both the headers should have been retrieved
assertTrue(it.hasNext()); assertTrue(firstFound);
header = it.next(); assertTrue(secondFound);
if(messageId.equals(header.getId())) {
assertHeadersMatch(message, header);
assertTrue(header.isRead());
messageFound = true;
} else if(messageId1.equals(header.getId())) {
assertHeadersMatch(message1, header);
assertFalse(header.isRead());
message1Found = true;
} else {
fail();
}
// No more headers
assertFalse(it.hasNext());
assertTrue(messageFound);
assertTrue(message1Found);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -950,6 +933,62 @@ public class H2DatabaseTest extends BriarTestCase {
assertEquals(m.getTimestamp(), h.getTimestamp()); assertEquals(m.getTimestamp(), h.getTimestamp());
} }
@Test
public void testAuthorStatus() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact and subscribe to a group
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addGroup(txn, group);
// Store a message from the contact - status VERIFIED
db.addMessage(txn, message, false);
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
// Store a message from an unknown author - status UNKNOWN
Author author1 = new Author(authorId1, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH]);
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message message1 = new TestMessage(messageId1, null, group, author1,
contentType, subject, timestamp, raw);
db.addMessage(txn, message1, false);
// Store an anonymous message - status ANONYMOUS
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
Message message2 = new TestMessage(messageId2, null, group, null,
contentType, subject, timestamp, raw);
db.addMessage(txn, message2, false);
// Retrieve the message headers (order is undefined)
Collection<MessageHeader> headers = db.getMessageHeaders(txn, groupId);
assertEquals(3, headers.size());
boolean firstFound = false, secondFound = false, thirdFound = false;
for(MessageHeader header : headers) {
if(messageId.equals(header.getId())) {
assertHeadersMatch(message, header);
assertEquals(Author.Status.VERIFIED, header.getAuthorStatus());
firstFound = true;
} else if(messageId1.equals(header.getId())) {
assertHeadersMatch(message1, header);
assertEquals(Author.Status.UNKNOWN, header.getAuthorStatus());
secondFound = true;
} else if(messageId2.equals(header.getId())) {
assertHeadersMatch(message2, header);
assertEquals(Author.Status.ANONYMOUS, header.getAuthorStatus());
thirdFound = true;
} else {
fail();
}
}
// All of the headers should have been retrieved
assertTrue(firstFound);
assertTrue(secondFound);
assertTrue(thirdFound);
db.commitTransaction(txn);
db.close();
}
@Test @Test
public void testReadFlag() throws Exception { public void testReadFlag() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);