Added spinners for selecting which identity to use.

(Although it isn't possible to create an identity yet...)
This commit is contained in:
akwizgran
2013-03-30 19:15:15 +00:00
parent 3309938467
commit 5800949b26
20 changed files with 432 additions and 228 deletions

View File

@@ -13,15 +13,15 @@
<string name="contact_connected">Connected</string> <string name="contact_connected">Connected</string>
<string name="format_contact_last_connected">Last connected &lt;br /&gt; %1$s</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="add_contact_title">Add a Contact</string>
<string name="choose_identity">Choose an identity to use with this contact:</string> <string name="your_identity">Your identity: </string>
<string name="wifi_not_available">Wi-Fi is not available on this device</string> <string name="wifi_not_available">Wi-Fi is NOT AVAILABLE</string>
<string name="wifi_disabled">Wi-Fi is OFF</string> <string name="wifi_disabled">Wi-Fi is OFF</string>
<string name="wifi_disconnected">Wi-Fi is DISCONNECTED</string> <string name="wifi_disconnected">Wi-Fi is DISCONNECTED</string>
<string name="format_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_not_available">Bluetooth is NOT AVAILABLE</string>
<string name="bluetooth_disabled">Bluetooth is OFF</string> <string name="bluetooth_disabled">Bluetooth is OFF</string>
<string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE</string> <string name="bluetooth_not_discoverable">Bluetooth is NOT DISCOVERABLE</string>
<string name="bluetooth_enabled">Bluetooth is DISCOVERABLE</string> <string name="bluetooth_enabled">Bluetooth is discoverable</string>
<string name="continue_button">Continue</string> <string name="continue_button">Continue</string>
<string name="your_invitation_code">Your invitation code is</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="enter_invitation_code">Please enter your contact\'s invitation code:</string>
@@ -43,7 +43,8 @@
<string name="format_from">From: %1$s</string> <string name="format_from">From: %1$s</string>
<string name="format_to">To: %1$s</string> <string name="format_to">To: %1$s</string>
<string name="compose_message_title">New Message</string> <string name="compose_message_title">New Message</string>
<string name="to">To:</string> <string name="from">From: </string>
<string name="to">To: </string>
<string name="anonymous">(Anonymous)</string> <string name="anonymous">(Anonymous)</string>
<string name="groups_title">Groups</string> <string name="groups_title">Groups</string>
<string name="compose_group_title">New Post</string> <string name="compose_group_title">New Post</string>

View File

@@ -0,0 +1,16 @@
package net.sf.briar.android;
import java.util.Comparator;
import net.sf.briar.api.Author;
public class AuthorNameComparator implements Comparator<Author> {
public static final AuthorNameComparator INSTANCE =
new AuthorNameComparator();
public int compare(Author a1, Author a2) {
return String.CASE_INSENSITIVE_ORDER.compare(a1.getName(),
a2.getName());
}
}

View File

@@ -0,0 +1,36 @@
package net.sf.briar.android;
import java.util.ArrayList;
import net.sf.briar.api.LocalAuthor;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
public class LocalAuthorNameSpinnerAdapter extends ArrayAdapter<LocalAuthor>
implements SpinnerAdapter {
public LocalAuthorNameSpinnerAdapter(Context context) {
super(context, android.R.layout.simple_spinner_item,
new ArrayList<LocalAuthor>());
}
@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

@@ -6,7 +6,6 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -122,10 +121,8 @@ OnClickListener, OnItemClickListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
// Wait for the headers to be displayed in the UI // Display the headers in the UI
CountDownLatch latch = new CountDownLatch(1); displayHeaders(headers);
displayHeaders(latch, headers);
latch.await();
} catch(NoSuchSubscriptionException e) { } catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO)) LOG.info("Subscription removed"); if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
finishOnUiThread(); finishOnUiThread();
@@ -134,25 +131,20 @@ OnClickListener, OnItemClickListener {
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) { } catch(InterruptedException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Interrupted while loading headers"); LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
}); });
} }
private void displayHeaders(final CountDownLatch latch, private void displayHeaders(final Collection<GroupMessageHeader> headers) {
final Collection<GroupMessageHeader> headers) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
try { adapter.clear();
adapter.clear(); for(GroupMessageHeader h : headers) adapter.add(h);
for(GroupMessageHeader h : headers) adapter.add(h); adapter.sort(AscendingHeaderComparator.INSTANCE);
adapter.sort(AscendingHeaderComparator.INSTANCE); selectFirstUnread();
selectFirstUnread();
} finally {
latch.countDown();
}
} }
}); });
} }
@@ -203,7 +195,7 @@ OnClickListener, OnItemClickListener {
} }
} else if(e instanceof MessageExpiredEvent) { } else if(e instanceof MessageExpiredEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
loadHeaders(); // FIXME: Don't reload everything loadHeaders();
} else if(e instanceof RatingChangedEvent) { } else if(e instanceof RatingChangedEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Rating changed, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Rating changed, reloading");
loadHeaders(); loadHeaders();

View File

@@ -11,7 +11,6 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -130,8 +129,6 @@ implements OnClickListener, DatabaseListener {
// Wait for the service to be bound and started // Wait for the service to be bound and started
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
// Load the subscribed groups from the DB // Load the subscribed groups from the DB
Collection<CountDownLatch> latches =
new ArrayList<CountDownLatch>();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for(Group g : db.getSubscriptions()) { for(Group g : db.getSubscriptions()) {
// Filter out restricted/unrestricted groups // Filter out restricted/unrestricted groups
@@ -141,9 +138,7 @@ implements OnClickListener, DatabaseListener {
Collection<GroupMessageHeader> headers = Collection<GroupMessageHeader> headers =
db.getMessageHeaders(g.getId()); db.getMessageHeaders(g.getId());
// Display the headers in the UI // Display the headers in the UI
CountDownLatch latch = new CountDownLatch(1); displayHeaders(g, headers);
displayHeaders(latch, g, headers);
latches.add(latch);
} catch(NoSuchSubscriptionException e) { } catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Subscription removed"); LOG.info("Subscription removed");
@@ -152,39 +147,33 @@ implements OnClickListener, DatabaseListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms"); LOG.info("Full load took " + duration + " ms");
// Wait for the headers to be displayed in the UI
for(CountDownLatch latch : latches) latch.await();
} catch(DbException e) { } catch(DbException e) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) { } catch(InterruptedException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Interrupted while loading headers"); LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
}); });
} }
private void displayHeaders(final CountDownLatch latch, final Group g, private void displayHeaders(final Group g,
final Collection<GroupMessageHeader> headers) { final Collection<GroupMessageHeader> headers) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
try { // Remove the old item, if any
// Remove the old item, if any GroupListItem item = findGroup(g.getId());
GroupListItem item = findGroup(g.getId()); if(item != null) adapter.remove(item);
if(item != null) adapter.remove(item); // Add a new item if there are any headers to display
// Add a new item if there are any headers to display if(!headers.isEmpty()) {
if(!headers.isEmpty()) { List<GroupMessageHeader> headerList =
List<GroupMessageHeader> headerList = new ArrayList<GroupMessageHeader>(headers);
new ArrayList<GroupMessageHeader>(headers); adapter.add(new GroupListItem(g, headerList));
adapter.add(new GroupListItem(g, headerList)); adapter.sort(GroupComparator.INSTANCE);
adapter.sort(GroupComparator.INSTANCE);
}
selectFirstUnread();
} finally {
latch.countDown();
} }
selectFirstUnread();
} }
}); });
} }
@@ -241,7 +230,7 @@ implements OnClickListener, DatabaseListener {
} }
} else if(e instanceof MessageExpiredEvent) { } else if(e instanceof MessageExpiredEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
loadHeaders(); // FIXME: Don't reload everything loadHeaders();
} else if(e instanceof SubscriptionRemovedEvent) { } else if(e instanceof SubscriptionRemovedEvent) {
// Reload the group, expecting NoSuchSubscriptionException // Reload the group, expecting NoSuchSubscriptionException
Group g = ((SubscriptionRemovedEvent) e).getGroup(); Group g = ((SubscriptionRemovedEvent) e).getGroup();
@@ -263,9 +252,7 @@ implements OnClickListener, DatabaseListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Partial load took " + duration + " ms"); LOG.info("Partial load took " + duration + " ms");
CountDownLatch latch = new CountDownLatch(1); displayHeaders(g, headers);
displayHeaders(latch, g, headers);
latch.await();
} catch(NoSuchSubscriptionException e) { } catch(NoSuchSubscriptionException e) {
if(LOG.isLoggable(INFO)) LOG.info("Subscription removed"); if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
removeGroup(g.getId()); removeGroup(g.getId());
@@ -274,7 +261,7 @@ implements OnClickListener, DatabaseListener {
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) { } catch(InterruptedException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Interrupted while loading headers"); LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }

View File

@@ -26,8 +26,8 @@ import net.sf.briar.android.widgets.HorizontalBorder;
import net.sf.briar.android.widgets.HorizontalSpace; import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.AuthorId; import net.sf.briar.api.AuthorId;
import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent; 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.DbException;
import net.sf.briar.api.db.NoSuchMessageException; import net.sf.briar.api.db.NoSuchMessageException;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
@@ -72,7 +72,7 @@ implements OnClickListener {
// Fields that are accessed from DB threads must be volatile // Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db; @Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
private volatile MessageId messageId = null; private volatile MessageId messageId = null;
private volatile AuthorId authorId = null; private volatile AuthorId authorId = null;
@@ -234,7 +234,7 @@ implements OnClickListener {
} }
private void setReadInDatabase(final boolean read) { private void setReadInDatabase(final boolean read) {
dbExecutor.execute(new Runnable() { dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
@@ -263,7 +263,7 @@ implements OnClickListener {
} }
private void loadMessageBody() { private void loadMessageBody() {
dbExecutor.execute(new Runnable() { dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
@@ -329,7 +329,7 @@ implements OnClickListener {
} }
private void setRatingInDatabase(final Rating r) { private void setRatingInDatabase(final Rating r) {
dbExecutor.execute(new Runnable() { dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();

View File

@@ -17,14 +17,17 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.R; import net.sf.briar.R;
import net.sf.briar.android.AuthorNameComparator;
import net.sf.briar.android.BriarActivity; import net.sf.briar.android.BriarActivity;
import net.sf.briar.android.BriarService; import net.sf.briar.android.BriarService;
import net.sf.briar.android.BriarService.BriarServiceConnection; import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.LocalAuthorNameSpinnerAdapter;
import net.sf.briar.android.widgets.CommonLayoutParams; import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalSpace; import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent; 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.DbException;
import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
@@ -47,7 +50,7 @@ import android.widget.TextView;
import com.google.inject.Inject; import com.google.inject.Inject;
public class WriteGroupMessageActivity extends BriarActivity public class WriteGroupMessageActivity extends BriarActivity
implements OnClickListener, OnItemSelectedListener { implements OnItemSelectedListener, OnClickListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(WriteGroupMessageActivity.class.getName()); Logger.getLogger(WriteGroupMessageActivity.class.getName());
@@ -56,16 +59,18 @@ implements OnClickListener, OnItemSelectedListener {
new BriarServiceConnection(); new BriarServiceConnection();
@Inject private BundleEncrypter bundleEncrypter; @Inject private BundleEncrypter bundleEncrypter;
private GroupNameSpinnerAdapter adapter = null; private LocalAuthorNameSpinnerAdapter fromAdapter = null;
private Spinner spinner = null; private GroupNameSpinnerAdapter toAdapter = null;
private Spinner fromSpinner = null, toSpinner = null;
private ImageButton sendButton = null; private ImageButton sendButton = null;
private EditText content = null; private EditText content = null;
// Fields that are accessed from DB threads must be volatile // Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db; @Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Inject private volatile MessageFactory messageFactory; @Inject private volatile MessageFactory messageFactory;
private volatile boolean restricted = false; private volatile boolean restricted = false;
private volatile LocalAuthor localAuthor = null;
private volatile Group group = null; private volatile Group group = null;
private volatile GroupId groupId = null; private volatile GroupId groupId = null;
private volatile MessageId parentId = null; private volatile MessageId parentId = null;
@@ -85,33 +90,51 @@ implements OnClickListener, OnItemSelectedListener {
layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP); layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
layout.setOrientation(VERTICAL); layout.setOrientation(VERTICAL);
LinearLayout actionBar = new LinearLayout(this); LinearLayout header = new LinearLayout(this);
actionBar.setLayoutParams(CommonLayoutParams.MATCH_WRAP); header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
actionBar.setOrientation(HORIZONTAL); header.setOrientation(HORIZONTAL);
actionBar.setGravity(CENTER_VERTICAL); header.setGravity(CENTER_VERTICAL);
TextView from = new TextView(this);
from.setTextSize(18);
from.setPadding(10, 10, 10, 10);
from.setText(R.string.from);
header.addView(from);
fromAdapter = new LocalAuthorNameSpinnerAdapter(this);
fromSpinner = new Spinner(this);
fromSpinner.setOnItemSelectedListener(this);
loadLocalAuthorList();
header.addView(fromSpinner);
header.addView(new HorizontalSpace(this));
sendButton = new ImageButton(this);
sendButton.setBackgroundResource(0);
sendButton.setImageResource(R.drawable.social_send_now);
sendButton.setEnabled(false); // Enabled when a group is selected
sendButton.setOnClickListener(this);
header.addView(sendButton);
layout.addView(header);
header = new LinearLayout(this);
header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
header.setOrientation(HORIZONTAL);
header.setGravity(CENTER_VERTICAL);
TextView to = new TextView(this); TextView to = new TextView(this);
to.setTextSize(18); to.setTextSize(18);
to.setPadding(10, 10, 10, 10); to.setPadding(10, 10, 10, 10);
to.setText(R.string.to); to.setText(R.string.to);
actionBar.addView(to); header.addView(to);
adapter = new GroupNameSpinnerAdapter(this); toAdapter = new GroupNameSpinnerAdapter(this);
spinner = new Spinner(this); toSpinner = new Spinner(this);
spinner.setAdapter(adapter); toSpinner.setAdapter(toAdapter);
spinner.setOnItemSelectedListener(this); toSpinner.setOnItemSelectedListener(this);
loadGroupList(); loadGroupList();
actionBar.addView(spinner); header.addView(toSpinner);
layout.addView(header);
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 = new EditText(this);
content.setPadding(10, 10, 10, 10); content.setPadding(10, 10, 10, 10);
@@ -128,20 +151,48 @@ implements OnClickListener, OnItemSelectedListener {
serviceConnection, 0); serviceConnection, 0);
} }
// FIXME: If restricted, only load groups the user can post to private void loadLocalAuthorList() {
private void loadGroupList() { dbUiExecutor.execute(new Runnable() {
dbExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
List<Group> postable = new ArrayList<Group>(); updateLocalAuthorList(db.getLocalAuthors());
} 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();
}
}
});
}
private void updateLocalAuthorList(
final Collection<LocalAuthor> localAuthors) {
runOnUiThread(new Runnable() {
public void run() {
fromAdapter.clear();
for(LocalAuthor a : localAuthors) fromAdapter.add(a);
fromAdapter.sort(AuthorNameComparator.INSTANCE);
}
});
}
private void loadGroupList() {
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
serviceConnection.waitForStartup();
List<Group> groups = new ArrayList<Group>();
if(restricted) { if(restricted) {
postable.addAll(db.getLocalGroups()); groups.addAll(db.getLocalGroups());
} else { } else {
for(Group g : db.getSubscriptions()) for(Group g : db.getSubscriptions())
if(!g.isRestricted()) postable.add(g); if(!g.isRestricted()) groups.add(g);
} }
updateGroupList(Collections.unmodifiableList(postable)); groups = Collections.unmodifiableList(groups);
updateGroupList(groups);
} catch(DbException e) { } catch(DbException e) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -156,13 +207,15 @@ implements OnClickListener, OnItemSelectedListener {
private void updateGroupList(final Collection<Group> groups) { private void updateGroupList(final Collection<Group> groups) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
int index = -1;
for(Group g : groups) { for(Group g : groups) {
if(g.getId().equals(groupId)) { if(g.getId().equals(groupId)) {
group = g; group = g;
spinner.setSelection(adapter.getCount()); index = toAdapter.getCount();
} }
adapter.add(g); toAdapter.add(g);
} }
if(index != -1) toSpinner.setSelection(index);
} }
}); });
} }
@@ -180,21 +233,45 @@ implements OnClickListener, OnItemSelectedListener {
unbindService(serviceConnection); unbindService(serviceConnection);
} }
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
if(parent == fromSpinner) {
localAuthor = fromAdapter.getItem(position);
} else if(parent == toSpinner) {
group = toAdapter.getItem(position);
groupId = group.getId();
sendButton.setEnabled(true);
}
}
public void onNothingSelected(AdapterView<?> parent) {
if(parent == fromSpinner) {
localAuthor = null;
} else if(parent == toSpinner) {
group = null;
groupId = null;
sendButton.setEnabled(false);
}
}
public void onClick(View view) { public void onClick(View view) {
if(group == null) throw new IllegalStateException(); if(group == null) throw new IllegalStateException();
try { try {
storeMessage(content.getText().toString().getBytes("UTF-8")); storeMessage(localAuthor, group,
content.getText().toString().getBytes("UTF-8"));
} catch(UnsupportedEncodingException e) { } catch(UnsupportedEncodingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
finish(); finish();
} }
private void storeMessage(final byte[] body) { private void storeMessage(final LocalAuthor localAuthor, final Group group,
dbExecutor.execute(new Runnable() { final byte[] body) {
dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
// FIXME: Anonymous/pseudonymous, restricted/unrestricted
Message m = messageFactory.createAnonymousMessage(parentId, Message m = messageFactory.createAnonymousMessage(parentId,
group, "text/plain", body); group, "text/plain", body);
db.addLocalGroupMessage(m); db.addLocalGroupMessage(m);
@@ -213,17 +290,4 @@ implements OnClickListener, OnItemSelectedListener {
} }
}); });
} }
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

@@ -1,21 +1,42 @@
package net.sf.briar.android.invitation; package net.sf.briar.android.invitation;
import static java.util.logging.Level.WARNING;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.android.AuthorNameComparator;
import net.sf.briar.android.BriarActivity; import net.sf.briar.android.BriarActivity;
import net.sf.briar.android.BriarService;
import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.api.AuthorId; import net.sf.briar.api.AuthorId;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.android.ReferenceManager; import net.sf.briar.api.android.ReferenceManager;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.invitation.InvitationListener; import net.sf.briar.api.invitation.InvitationListener;
import net.sf.briar.api.invitation.InvitationState; import net.sf.briar.api.invitation.InvitationState;
import net.sf.briar.api.invitation.InvitationTask; import net.sf.briar.api.invitation.InvitationTask;
import net.sf.briar.api.invitation.InvitationTaskFactory; import net.sf.briar.api.invitation.InvitationTaskFactory;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.widget.ArrayAdapter;
import com.google.inject.Inject; import com.google.inject.Inject;
public class AddContactActivity extends BriarActivity public class AddContactActivity extends BriarActivity
implements InvitationListener { implements InvitationListener {
private static final Logger LOG =
Logger.getLogger(AddContactActivity.class.getName());
private final BriarServiceConnection serviceConnection =
new BriarServiceConnection();
@Inject private BundleEncrypter bundleEncrypter; @Inject private BundleEncrypter bundleEncrypter;
@Inject private CryptoComponent crypto; @Inject private CryptoComponent crypto;
@Inject private InvitationTaskFactory invitationTaskFactory; @Inject private InvitationTaskFactory invitationTaskFactory;
@@ -33,6 +54,10 @@ implements InvitationListener {
private boolean localMatched = false, remoteMatched = false; private boolean localMatched = false, remoteMatched = false;
private String contactName = null; private String contactName = null;
// Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(null); super.onCreate(null);
@@ -105,6 +130,10 @@ implements InvitationListener {
} }
} }
} }
// Bind to the service so we can wait for the DB to be opened
bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
} }
@Override @Override
@@ -156,6 +185,34 @@ implements InvitationListener {
setView(view); setView(view);
} }
void loadLocalAuthorList(final ArrayAdapter<LocalAuthor> adapter) {
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
serviceConnection.waitForStartup();
updateLocalAuthorList(adapter, db.getLocalAuthors());
} 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();
}
}
});
}
private void updateLocalAuthorList(final ArrayAdapter<LocalAuthor> adapter,
final Collection<LocalAuthor> localAuthors) {
runOnUiThread(new Runnable() {
public void run() {
adapter.clear();
for(LocalAuthor a : localAuthors) adapter.add(a);
adapter.sort(AuthorNameComparator.INSTANCE);
}
});
}
void setLocalAuthorId(AuthorId localAuthorId) { void setLocalAuthorId(AuthorId localAuthorId) {
this.localAuthorId = localAuthorId; this.localAuthorId = localAuthorId;
} }

View File

@@ -9,8 +9,8 @@ abstract class AddContactView extends LinearLayout {
protected AddContactActivity container = null; protected AddContactActivity container = null;
AddContactView(Context context) { AddContactView(Context ctx) {
super(context); super(ctx);
} }
void init(AddContactActivity container) { void init(AddContactActivity container) {

View File

@@ -1,16 +1,25 @@
package net.sf.briar.android.invitation; package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import net.sf.briar.R; import net.sf.briar.R;
import net.sf.briar.android.LocalAuthorNameSpinnerAdapter;
import net.sf.briar.android.widgets.CommonLayoutParams; import net.sf.briar.android.widgets.CommonLayoutParams;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
public class NetworkSetupView extends AddContactView public class NetworkSetupView extends AddContactView
implements WifiStateListener, BluetoothStateListener, OnClickListener { implements WifiStateListener, BluetoothStateListener, OnItemSelectedListener,
OnClickListener {
private LocalAuthorNameSpinnerAdapter adapter = null;
private Spinner spinner = null;
private Button continueButton = null; private Button continueButton = null;
NetworkSetupView(Context ctx) { NetworkSetupView(Context ctx) {
@@ -20,13 +29,25 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
void populate() { void populate() {
removeAllViews(); removeAllViews();
Context ctx = getContext(); Context ctx = getContext();
TextView chooseIdentity = new TextView(ctx);
chooseIdentity.setTextSize(14);
chooseIdentity.setPadding(10, 10, 10, 10);
chooseIdentity.setText(R.string.choose_identity);
addView(chooseIdentity);
// FIXME: Add a spinner for choosing which identity to use LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
TextView yourIdentity = new TextView(ctx);
yourIdentity.setTextSize(18);
yourIdentity.setPadding(10, 10, 10, 10);
yourIdentity.setText(R.string.your_identity);
innerLayout.addView(yourIdentity);
adapter = new LocalAuthorNameSpinnerAdapter(ctx);
spinner = new Spinner(ctx);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(this);
container.loadLocalAuthorList(adapter);
innerLayout.addView(spinner);
addView(innerLayout);
WifiWidget wifi = new WifiWidget(ctx); WifiWidget wifi = new WifiWidget(ctx);
wifi.init(this); wifi.init(this);
@@ -70,8 +91,16 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
else continueButton.setEnabled(false); else continueButton.setEnabled(false);
} }
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
container.setLocalAuthorId(adapter.getItem(position).getId());
}
public void onNothingSelected(AdapterView<?> parent) {
container.setLocalAuthorId(null);
}
public void onClick(View view) { public void onClick(View view) {
// Continue
container.setView(new InvitationCodeView(container)); container.setView(new InvitationCodeView(container));
} }
} }

View File

@@ -6,7 +6,6 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -118,10 +117,8 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
// Wait for the headers to be displayed in the UI // Display the headers in the UI
CountDownLatch latch = new CountDownLatch(1); displayHeaders(headers);
displayHeaders(latch, headers);
latch.await();
} catch(NoSuchContactException e) { } catch(NoSuchContactException e) {
if(LOG.isLoggable(INFO)) LOG.info("Contact removed"); if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
finishOnUiThread(); finishOnUiThread();
@@ -130,25 +127,21 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) { } catch(InterruptedException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Interrupted while loading headers"); LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
}); });
} }
private void displayHeaders(final CountDownLatch latch, private void displayHeaders(
final Collection<PrivateMessageHeader> headers) { final Collection<PrivateMessageHeader> headers) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
try { adapter.clear();
adapter.clear(); for(PrivateMessageHeader h : headers) adapter.add(h);
for(PrivateMessageHeader h : headers) adapter.add(h); adapter.sort(AscendingHeaderComparator.INSTANCE);
adapter.sort(AscendingHeaderComparator.INSTANCE); selectFirstUnread();
selectFirstUnread();
} finally {
latch.countDown();
}
} }
}); });
} }
@@ -199,7 +192,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
} }
} else if(e instanceof MessageExpiredEvent) { } else if(e instanceof MessageExpiredEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
loadHeaders(); // FIXME: Don't reload everything loadHeaders();
} else if(e instanceof PrivateMessageAddedEvent) { } else if(e instanceof PrivateMessageAddedEvent) {
PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e; PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
if(p.getContactId().equals(contactId)) { if(p.getContactId().equals(contactId)) {

View File

@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -102,8 +101,6 @@ implements OnClickListener, DatabaseListener {
// Wait for the service to be bound and started // Wait for the service to be bound and started
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
// Load the contact list from the database // Load the contact list from the database
Collection<CountDownLatch> latches =
new ArrayList<CountDownLatch>();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for(Contact c : db.getContacts()) { for(Contact c : db.getContacts()) {
try { try {
@@ -111,9 +108,7 @@ implements OnClickListener, DatabaseListener {
Collection<PrivateMessageHeader> headers = Collection<PrivateMessageHeader> headers =
db.getPrivateMessageHeaders(c.getId()); db.getPrivateMessageHeaders(c.getId());
// Display the headers in the UI // Display the headers in the UI
CountDownLatch latch = new CountDownLatch(1); displayHeaders(c, headers);
displayHeaders(latch, c, headers);
latches.add(latch);
} catch(NoSuchContactException e) { } catch(NoSuchContactException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Contact removed"); LOG.info("Contact removed");
@@ -122,39 +117,33 @@ implements OnClickListener, DatabaseListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms"); LOG.info("Full load took " + duration + " ms");
// Wait for the headers to be displayed in the UI
for(CountDownLatch latch : latches) latch.await();
} catch(DbException e) { } catch(DbException e) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) { } catch(InterruptedException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Interrupted while loading headers"); LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
}); });
} }
private void displayHeaders(final CountDownLatch latch, final Contact c, private void displayHeaders(final Contact c,
final Collection<PrivateMessageHeader> headers) { final Collection<PrivateMessageHeader> headers) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
try { // Remove the old item, if any
// Remove the old item, if any ConversationListItem item = findConversation(c.getId());
ConversationListItem item = findConversation(c.getId()); if(item != null) adapter.remove(item);
if(item != null) adapter.remove(item); // Add a new item if there are any headers to display
// Add a new item if there are any headers to display if(!headers.isEmpty()) {
if(!headers.isEmpty()) { List<PrivateMessageHeader> headerList =
List<PrivateMessageHeader> headerList = new ArrayList<PrivateMessageHeader>(headers);
new ArrayList<PrivateMessageHeader>(headers); adapter.add(new ConversationListItem(c, headerList));
adapter.add(new ConversationListItem(c, headerList)); adapter.sort(ConversationComparator.INSTANCE);
adapter.sort(ConversationComparator.INSTANCE);
}
selectFirstUnread();
} finally {
latch.countDown();
} }
selectFirstUnread();
} }
}); });
} }
@@ -203,7 +192,7 @@ implements OnClickListener, DatabaseListener {
loadHeaders(((ContactRemovedEvent) e).getContactId()); loadHeaders(((ContactRemovedEvent) e).getContactId());
} else if(e instanceof MessageExpiredEvent) { } else if(e instanceof MessageExpiredEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
loadHeaders(); // FIXME: Don't reload everything loadHeaders();
} else if(e instanceof PrivateMessageAddedEvent) { } else if(e instanceof PrivateMessageAddedEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
loadHeaders(((PrivateMessageAddedEvent) e).getContactId()); loadHeaders(((PrivateMessageAddedEvent) e).getContactId());
@@ -222,9 +211,7 @@ implements OnClickListener, DatabaseListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Partial load took " + duration + " ms"); LOG.info("Partial load took " + duration + " ms");
CountDownLatch latch = new CountDownLatch(1); displayHeaders(contact, headers);
displayHeaders(latch, contact, headers);
latch.await();
} catch(NoSuchContactException e) { } catch(NoSuchContactException e) {
if(LOG.isLoggable(INFO)) LOG.info("Contact removed"); if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
removeConversation(c); removeConversation(c);
@@ -233,7 +220,7 @@ implements OnClickListener, DatabaseListener {
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) { } catch(InterruptedException e) {
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Interrupted while loading headers"); LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }

View File

@@ -21,8 +21,8 @@ import net.sf.briar.android.widgets.HorizontalBorder;
import net.sf.briar.android.widgets.HorizontalSpace; import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent; 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.DbException;
import net.sf.briar.api.db.NoSuchMessageException; import net.sf.briar.api.db.NoSuchMessageException;
import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.MessageId;
@@ -61,7 +61,7 @@ implements OnClickListener {
// Fields that are accessed from DB threads must be volatile // Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db; @Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
private volatile MessageId messageId = null; private volatile MessageId messageId = null;
@Override @Override
@@ -187,7 +187,7 @@ implements OnClickListener {
} }
private void setReadInDatabase(final boolean read) { private void setReadInDatabase(final boolean read) {
dbExecutor.execute(new Runnable() { dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
@@ -216,7 +216,7 @@ implements OnClickListener {
} }
private void loadMessageBody() { private void loadMessageBody() {
dbExecutor.execute(new Runnable() { dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();

View File

@@ -19,11 +19,13 @@ import net.sf.briar.android.BriarService;
import net.sf.briar.android.BriarService.BriarServiceConnection; import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.widgets.CommonLayoutParams; import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalSpace; import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact; import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.android.BundleEncrypter; import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent; 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.DbException;
import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageFactory; import net.sf.briar.api.messaging.MessageFactory;
@@ -44,7 +46,7 @@ import android.widget.TextView;
import com.google.inject.Inject; import com.google.inject.Inject;
public class WritePrivateMessageActivity extends BriarActivity public class WritePrivateMessageActivity extends BriarActivity
implements OnClickListener, OnItemSelectedListener { implements OnItemSelectedListener, OnClickListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(WritePrivateMessageActivity.class.getName()); Logger.getLogger(WritePrivateMessageActivity.class.getName());
@@ -53,6 +55,7 @@ implements OnClickListener, OnItemSelectedListener {
new BriarServiceConnection(); new BriarServiceConnection();
@Inject private BundleEncrypter bundleEncrypter; @Inject private BundleEncrypter bundleEncrypter;
private TextView from = null;
private ContactNameSpinnerAdapter adapter = null; private ContactNameSpinnerAdapter adapter = null;
private Spinner spinner = null; private Spinner spinner = null;
private ImageButton sendButton = null; private ImageButton sendButton = null;
@@ -60,8 +63,9 @@ implements OnClickListener, OnItemSelectedListener {
// Fields that are accessed from DB threads must be volatile // Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db; @Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor; @Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Inject private volatile MessageFactory messageFactory; @Inject private volatile MessageFactory messageFactory;
private volatile LocalAuthor localAuthor = null;
private volatile ContactId contactId = null; private volatile ContactId contactId = null;
private volatile MessageId parentId = null; private volatile MessageId parentId = null;
@@ -79,33 +83,46 @@ implements OnClickListener, OnItemSelectedListener {
layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP); layout.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
layout.setOrientation(VERTICAL); layout.setOrientation(VERTICAL);
LinearLayout actionBar = new LinearLayout(this); LinearLayout header = new LinearLayout(this);
actionBar.setLayoutParams(CommonLayoutParams.MATCH_WRAP); header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
actionBar.setOrientation(HORIZONTAL); header.setOrientation(HORIZONTAL);
actionBar.setGravity(CENTER_VERTICAL); header.setGravity(CENTER_VERTICAL);
from = new TextView(this);
from.setTextSize(18);
from.setMaxLines(1);
from.setPadding(10, 10, 10, 10);
from.setText(R.string.from);
header.addView(from);
header.addView(new HorizontalSpace(this));
sendButton = new ImageButton(this);
sendButton.setBackgroundResource(0);
sendButton.setImageResource(R.drawable.social_send_now);
sendButton.setEnabled(false); // Enabled after loading the local author
sendButton.setOnClickListener(this);
header.addView(sendButton);
layout.addView(header);
header = new LinearLayout(this);
header.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
header.setOrientation(HORIZONTAL);
header.setGravity(CENTER_VERTICAL);
TextView to = new TextView(this); TextView to = new TextView(this);
to.setTextSize(18); to.setTextSize(18);
to.setPadding(10, 10, 10, 10); to.setPadding(10, 10, 10, 10);
to.setText(R.string.to); to.setText(R.string.to);
actionBar.addView(to); header.addView(to);
adapter = new ContactNameSpinnerAdapter(this); adapter = new ContactNameSpinnerAdapter(this);
spinner = new Spinner(this); spinner = new Spinner(this);
spinner.setAdapter(adapter); spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(this); spinner.setOnItemSelectedListener(this);
loadContactList(); loadContactList();
actionBar.addView(spinner); header.addView(spinner);
layout.addView(header);
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 = new EditText(this);
content.setPadding(10, 10, 10, 10); content.setPadding(10, 10, 10, 10);
@@ -123,7 +140,7 @@ implements OnClickListener, OnItemSelectedListener {
} }
private void loadContactList() { private void loadContactList() {
dbExecutor.execute(new Runnable() { dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
@@ -142,11 +159,12 @@ implements OnClickListener, OnItemSelectedListener {
private void updateContactList(final Collection<Contact> contacts) { private void updateContactList(final Collection<Contact> contacts) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
int index = -1;
for(Contact c : contacts) { for(Contact c : contacts) {
if(c.getId().equals(contactId)) if(c.getId().equals(contactId)) index = adapter.getCount();
spinner.setSelection(adapter.getCount());
adapter.add(c); adapter.add(c);
} }
if(index != -1) spinner.setSelection(index);
} }
}); });
} }
@@ -164,18 +182,55 @@ implements OnClickListener, OnItemSelectedListener {
unbindService(serviceConnection); unbindService(serviceConnection);
} }
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
Contact c = adapter.getItem(position);
loadLocalAuthor(c.getLocalAuthorId());
contactId = c.getId();
}
public void onNothingSelected(AdapterView<?> parent) {
contactId = null;
sendButton.setEnabled(false);
}
private void loadLocalAuthor(final AuthorId a) {
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
serviceConnection.waitForStartup();
localAuthor = db.getLocalAuthor(a);
runOnUiThread(new Runnable() {
public void run() {
sendButton.setEnabled(true);
}
});
} 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();
}
}
});
}
public void onClick(View view) { public void onClick(View view) {
if(contactId == null) throw new IllegalStateException(); if(localAuthor == null || contactId == null)
throw new IllegalStateException();
try { try {
storeMessage(content.getText().toString().getBytes("UTF-8")); storeMessage(localAuthor, contactId,
content.getText().toString().getBytes("UTF-8"));
} catch(UnsupportedEncodingException e) { } catch(UnsupportedEncodingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
finish(); finish();
} }
private void storeMessage(final byte[] body) { private void storeMessage(final LocalAuthor localAuthor,
dbExecutor.execute(new Runnable() { final ContactId contactId, final byte[] body) {
dbUiExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
serviceConnection.waitForStartup(); serviceConnection.waitForStartup();
@@ -197,15 +252,4 @@ implements OnClickListener, OnItemSelectedListener {
} }
}); });
} }
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
contactId = adapter.getItem(position).getId();
sendButton.setEnabled(true);
}
public void onNothingSelected(AdapterView<?> parent) {
contactId = null;
sendButton.setEnabled(false);
}
} }

View File

@@ -4,11 +4,14 @@ public class Contact {
private final ContactId id; private final ContactId id;
private final Author author; private final Author author;
private final AuthorId localAuthorId;
private final long lastConnected; private final long lastConnected;
public Contact(ContactId id, Author author, long lastConnected) { public Contact(ContactId id, Author author, AuthorId localAuthorId,
long lastConnected) {
this.id = id; this.id = id;
this.author = author; this.author = author;
this.localAuthorId = localAuthorId;
this.lastConnected = lastConnected; this.lastConnected = lastConnected;
} }
@@ -20,6 +23,10 @@ public class Contact {
return author; return author;
} }
public AuthorId getLocalAuthorId() {
return localAuthorId;
}
public long getLastConnected() { public long getLastConnected() {
return lastConnected; return lastConnected;
} }

View File

@@ -1,22 +1,9 @@
package net.sf.briar.api.db.event; package net.sf.briar.api.db.event;
import java.util.Collection;
import net.sf.briar.api.messaging.MessageId;
/** /**
* An event that is broadcast when one or messages expire from the database, * An event that is broadcast when one or messages expire from the database,
* potentially changing the database's retention time. * potentially changing the database's retention time.
*/ */
public class MessageExpiredEvent extends DatabaseEvent { public class MessageExpiredEvent extends DatabaseEvent {
private final Collection<MessageId> expired;
public MessageExpiredEvent(Collection<MessageId> expired) {
this.expired = expired;
}
public Collection<MessageId> getMessageIds() {
return expired;
}
} }

View File

@@ -2045,9 +2045,8 @@ DatabaseCleaner.Callback {
while(freeSpace < MIN_FREE_SPACE) { while(freeSpace < MIN_FREE_SPACE) {
boolean expired = expireMessages(BYTES_PER_SWEEP); boolean expired = expireMessages(BYTES_PER_SWEEP);
if(freeSpace < CRITICAL_FREE_SPACE && !expired) { if(freeSpace < CRITICAL_FREE_SPACE && !expired) {
// FIXME: Work out what to do here - the amount of free space // FIXME: Work out what to do here
// is critically low and there are no messages left to expire throw new Error("Disk space is critically low");
throw new Error("Disk space is critical");
} }
Thread.yield(); Thread.yield();
freeSpace = db.getFreeSpace(); freeSpace = db.getFreeSpace();
@@ -2086,7 +2085,7 @@ DatabaseCleaner.Callback {
messageLock.writeLock().unlock(); messageLock.writeLock().unlock();
} }
if(expired.isEmpty()) return false; if(expired.isEmpty()) return false;
callListeners(new MessageExpiredEvent(expired)); callListeners(new MessageExpiredEvent());
return true; return true;
} }

View File

@@ -1155,7 +1155,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT authorId, name, publicKey, lastConnected" String sql = "SELECT authorId, name, publicKey, localAuthorId,"
+ " lastConnected"
+ " FROM contacts AS c" + " FROM contacts AS c"
+ " JOIN connectionTimes AS ct" + " JOIN connectionTimes AS ct"
+ " ON c.contactId = ct.contactId" + " ON c.contactId = ct.contactId"
@@ -1167,11 +1168,12 @@ abstract class JdbcDatabase implements Database<Connection> {
AuthorId authorId = new AuthorId(rs.getBytes(1)); AuthorId authorId = new AuthorId(rs.getBytes(1));
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); byte[] publicKey = rs.getBytes(3);
long lastConnected = rs.getLong(4); AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
long lastConnected = rs.getLong(5);
rs.close(); rs.close();
ps.close(); ps.close();
Author author = new Author(authorId, name, publicKey); Author author = new Author(authorId, name, publicKey);
return new Contact(c, author, lastConnected); return new Contact(c, author, localAuthorId, lastConnected);
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(rs); tryToClose(rs);
tryToClose(ps); tryToClose(ps);
@@ -1205,7 +1207,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT c.contactId, authorId, name, publicKey," String sql = "SELECT c.contactId, authorId, name, publicKey,"
+ " lastConnected" + " localAuthorId, lastConnected"
+ " FROM contacts AS c" + " FROM contacts AS c"
+ " JOIN connectionTimes AS ct" + " JOIN connectionTimes AS ct"
+ " ON c.contactId = ct.contactId"; + " ON c.contactId = ct.contactId";
@@ -1217,9 +1219,11 @@ abstract class JdbcDatabase implements Database<Connection> {
AuthorId authorId = new AuthorId(rs.getBytes(2)); AuthorId authorId = new AuthorId(rs.getBytes(2));
String name = rs.getString(3); String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4); byte[] publicKey = rs.getBytes(4);
long lastConnected = rs.getLong(5); AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
long lastConnected = rs.getLong(6);
Author author = new Author(authorId, name, publicKey); Author author = new Author(authorId, name, publicKey);
contacts.add(new Contact(contactId, author, lastConnected)); contacts.add(new Contact(contactId, author, localAuthorId,
lastConnected));
} }
rs.close(); rs.close();
ps.close(); ps.close();

View File

@@ -134,6 +134,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
} else if(now >= creationTime) { } else if(now >= creationTime) {
incomingNew.put(k, s); incomingNew.put(k, s);
} else { } else {
// FIXME: Work out what to do here
throw new Error("Clock has moved backwards"); throw new Error("Clock has moved backwards");
} }
} }

View File

@@ -102,7 +102,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
transportProperties = new TransportProperties(Collections.singletonMap( transportProperties = new TransportProperties(Collections.singletonMap(
"foo", "bar")); "foo", "bar"));
contactId = new ContactId(234); contactId = new ContactId(234);
contact = new Contact(contactId, author, timestamp); contact = new Contact(contactId, author, localAuthorId, timestamp);
endpoint = new Endpoint(contactId, transportId, 123, true); endpoint = new Endpoint(contactId, transportId, 123, true);
temporarySecret = new TemporarySecret(contactId, transportId, 123, temporarySecret = new TemporarySecret(contactId, transportId, 123,
false, 234, new byte[32], 345, 456, new byte[4]); false, 234, new byte[32], 345, 456, new byte[4]);