mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 21:29:54 +01:00
Changed the root package from net.sf.briar to org.briarproject.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.Contact;
|
||||
|
||||
public class ContactItem {
|
||||
|
||||
public static final ContactItem NEW = new ContactItem(null);
|
||||
|
||||
private final Contact contact;
|
||||
|
||||
public ContactItem(Contact contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class ContactItemComparator implements Comparator<ContactItem> {
|
||||
|
||||
public static final ContactItemComparator INSTANCE =
|
||||
new ContactItemComparator();
|
||||
|
||||
public int compare(ContactItem a, ContactItem b) {
|
||||
if(a == b) return 0;
|
||||
if(a == ContactItem.NEW) return 1;
|
||||
if(b == ContactItem.NEW) return -1;
|
||||
String aName = a.getContact().getAuthor().getName();
|
||||
String bName = b.getContact().getAuthor().getName();
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import static android.view.Gravity.CENTER_HORIZONTAL;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.invitation.AddContactActivity;
|
||||
import org.briarproject.android.util.HorizontalBorder;
|
||||
import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.AuthorId;
|
||||
import org.briarproject.api.Contact;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.android.DatabaseUiExecutor;
|
||||
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.event.ContactAddedEvent;
|
||||
import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
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.transport.ConnectionListener;
|
||||
import org.briarproject.api.transport.ConnectionRegistry;
|
||||
import roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class ContactListActivity extends RoboActivity
|
||||
implements OnClickListener, OnItemClickListener, EventListener,
|
||||
ConnectionListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ContactListActivity.class.getName());
|
||||
|
||||
@Inject private ConnectionRegistry connectionRegistry;
|
||||
private ContactListAdapter adapter = null;
|
||||
private ListView list = null;
|
||||
private ListLoadingProgressBar loading = null;
|
||||
private ImageButton addContactButton = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
layout.setOrientation(VERTICAL);
|
||||
layout.setGravity(CENTER_HORIZONTAL);
|
||||
|
||||
adapter = new ContactListAdapter(this);
|
||||
list = new ListView(this);
|
||||
// Give me all the width and all the unused height
|
||||
list.setLayoutParams(MATCH_WRAP_1);
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(this);
|
||||
layout.addView(list);
|
||||
|
||||
// Show a progress bar while the list is loading
|
||||
list.setVisibility(GONE);
|
||||
loading = new ListLoadingProgressBar(this);
|
||||
layout.addView(loading);
|
||||
|
||||
layout.addView(new HorizontalBorder(this));
|
||||
|
||||
addContactButton = new ImageButton(this);
|
||||
addContactButton.setBackgroundResource(0);
|
||||
addContactButton.setImageResource(R.drawable.social_add_person);
|
||||
addContactButton.setOnClickListener(this);
|
||||
layout.addView(addContactButton);
|
||||
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
db.addListener(this);
|
||||
connectionRegistry.addListener(this);
|
||||
loadContacts();
|
||||
}
|
||||
|
||||
private void loadContacts() {
|
||||
clearContacts();
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Map<ContactId, Long> times = db.getLastConnected();
|
||||
for(Contact c : db.getContacts()) {
|
||||
Long lastConnected = times.get(c.getId());
|
||||
if(lastConnected == null) continue;
|
||||
try {
|
||||
GroupId inbox = db.getInboxGroupId(c.getId());
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(c.getId());
|
||||
displayContact(c, lastConnected, inbox, headers);
|
||||
} catch(NoSuchContactException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Contact removed");
|
||||
}
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Full load took " + duration + " ms");
|
||||
hideProgressBar();
|
||||
} 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 clearContacts() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(GONE);
|
||||
loading.setVisibility(VISIBLE);
|
||||
adapter.clear();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayContact(final Contact c, final long lastConnected,
|
||||
final GroupId inbox, final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
boolean connected = connectionRegistry.isConnected(c.getId());
|
||||
// Remove the old item, if any
|
||||
ContactListItem item = findItem(c.getId());
|
||||
if(item != null) adapter.remove(item);
|
||||
// Add a new item
|
||||
adapter.add(new ContactListItem(c, connected, lastConnected,
|
||||
inbox, headers));
|
||||
adapter.sort(ItemComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideProgressBar() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ContactListItem findItem(ContactId c) {
|
||||
int count = adapter.getCount();
|
||||
for(int i = 0; i < count; i++) {
|
||||
ContactListItem item = adapter.getItem(i);
|
||||
if(item.getContact().getId().equals(c)) return item;
|
||||
}
|
||||
return null; // Not found
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
db.removeListener(this);
|
||||
connectionRegistry.removeListener(this);
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
startActivity(new Intent(this, AddContactActivity.class));
|
||||
}
|
||||
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
ContactListItem item = adapter.getItem(position);
|
||||
ContactId contactId = item.getContact().getId();
|
||||
String contactName = item.getContact().getAuthor().getName();
|
||||
GroupId inbox = item.getInboxGroupId();
|
||||
AuthorId localAuthorId = item.getContact().getLocalAuthorId();
|
||||
Intent i = new Intent(this, ConversationActivity.class);
|
||||
i.putExtra("org.briarproject.CONTACT_ID", contactId.getInt());
|
||||
i.putExtra("org.briarproject.CONTACT_NAME", contactName);
|
||||
i.putExtra("org.briarproject.GROUP_ID", inbox.getBytes());
|
||||
i.putExtra("org.briarproject.LOCAL_AUTHOR_ID", localAuthorId.getBytes());
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if(e instanceof ContactAddedEvent) {
|
||||
loadContacts();
|
||||
} else if(e instanceof ContactRemovedEvent) {
|
||||
// Reload the conversation, expecting NoSuchContactException
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Contact removed, reloading");
|
||||
reloadContact(((ContactRemovedEvent) e).getContactId());
|
||||
} else if(e instanceof MessageAddedEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
ContactId source = ((MessageAddedEvent) e).getContactId();
|
||||
if(source == null) loadContacts();
|
||||
else reloadContact(source);
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
|
||||
loadContacts();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadContact(final ContactId c) {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(c);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Partial load took " + duration + " ms");
|
||||
updateItem(c, headers);
|
||||
} catch(NoSuchContactException e) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
|
||||
removeItem(c);
|
||||
} 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 updateItem(final ContactId c,
|
||||
final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
ContactListItem item = findItem(c);
|
||||
if(item != null) item.setHeaders(headers);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void removeItem(final ContactId c) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
ContactListItem item = findItem(c);
|
||||
if(item != null) {
|
||||
adapter.remove(item);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void contactConnected(ContactId c) {
|
||||
setConnected(c, true);
|
||||
}
|
||||
|
||||
public void contactDisconnected(ContactId c) {
|
||||
setConnected(c, false);
|
||||
}
|
||||
|
||||
private void setConnected(final ContactId c, final boolean connected) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
ContactListItem item = findItem(c);
|
||||
if(item == null) return;
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Updating connection time");
|
||||
item.setConnected(connected);
|
||||
item.setLastConnected(System.currentTimeMillis());
|
||||
list.invalidateViews();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ItemComparator implements Comparator<ContactListItem> {
|
||||
|
||||
private static final ItemComparator INSTANCE = new ItemComparator();
|
||||
|
||||
public int compare(ContactListItem a, ContactListItem b) {
|
||||
String aName = a.getContact().getAuthor().getName();
|
||||
String bName = b.getContact().getAuthor().getName();
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import static android.view.Gravity.CENTER_VERTICAL;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.briarproject.R;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.Html;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
class ContactListAdapter extends ArrayAdapter<ContactListItem> {
|
||||
|
||||
ContactListAdapter(Context ctx) {
|
||||
super(ctx, android.R.layout.simple_expandable_list_item_1,
|
||||
new ArrayList<ContactListItem>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ContactListItem item = getItem(position);
|
||||
Context ctx = getContext();
|
||||
LinearLayout layout = new LinearLayout(ctx);
|
||||
layout.setOrientation(HORIZONTAL);
|
||||
layout.setGravity(CENTER_VERTICAL);
|
||||
Resources res = ctx.getResources();
|
||||
if(item.getUnreadCount() > 0)
|
||||
layout.setBackgroundColor(res.getColor(R.color.unread_background));
|
||||
|
||||
ImageView bulb = new ImageView(ctx);
|
||||
bulb.setPadding(5, 5, 5, 5);
|
||||
if(item.isConnected())
|
||||
bulb.setImageResource(R.drawable.contact_connected);
|
||||
else bulb.setImageResource(R.drawable.contact_disconnected);
|
||||
layout.addView(bulb);
|
||||
|
||||
TextView name = new TextView(ctx);
|
||||
// Give me all the unused width
|
||||
name.setLayoutParams(WRAP_WRAP_1);
|
||||
name.setTextSize(18);
|
||||
name.setMaxLines(1);
|
||||
name.setPadding(0, 10, 10, 10);
|
||||
int unread = item.getUnreadCount();
|
||||
String contactName = item.getContact().getAuthor().getName();
|
||||
if(unread > 0) name.setText(contactName + " (" + unread + ")");
|
||||
else name.setText(contactName);
|
||||
layout.addView(name);
|
||||
|
||||
TextView connected = new TextView(ctx);
|
||||
connected.setTextSize(14);
|
||||
connected.setPadding(0, 10, 10, 10);
|
||||
if(item.isConnected()) {
|
||||
connected.setText(R.string.contact_connected);
|
||||
} else {
|
||||
String format = res.getString(R.string.format_last_connected);
|
||||
long then = item.getLastConnected();
|
||||
CharSequence ago = DateUtils.getRelativeTimeSpanString(then);
|
||||
connected.setText(Html.fromHtml(String.format(format, ago)));
|
||||
}
|
||||
layout.addView(connected);
|
||||
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.briarproject.api.Contact;
|
||||
import org.briarproject.api.db.MessageHeader;
|
||||
import org.briarproject.api.messaging.GroupId;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ContactListItem {
|
||||
|
||||
private final Contact contact;
|
||||
private final GroupId inbox;
|
||||
private boolean connected;
|
||||
private long lastConnected;
|
||||
private boolean empty;
|
||||
private long timestamp;
|
||||
private int unread;
|
||||
|
||||
ContactListItem(Contact contact, boolean connected, long lastConnected,
|
||||
GroupId inbox, Collection<MessageHeader> headers) {
|
||||
this.contact = contact;
|
||||
this.inbox = inbox;
|
||||
this.connected = connected;
|
||||
this.lastConnected = lastConnected;
|
||||
setHeaders(headers);
|
||||
}
|
||||
|
||||
void setHeaders(Collection<MessageHeader> headers) {
|
||||
empty = headers.isEmpty();
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
if(!empty) {
|
||||
for(MessageHeader h : headers) {
|
||||
if(h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
|
||||
if(!h.isRead()) unread++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Contact getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
GroupId getInboxGroupId() {
|
||||
return inbox;
|
||||
}
|
||||
|
||||
long getLastConnected() {
|
||||
return lastConnected;
|
||||
}
|
||||
|
||||
void setLastConnected(long lastConnected) {
|
||||
this.lastConnected = lastConnected;
|
||||
}
|
||||
|
||||
boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
int getUnreadCount() {
|
||||
return unread;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import static android.view.Gravity.CENTER_HORIZONTAL;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.HorizontalBorder;
|
||||
import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.AuthorId;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.android.DatabaseUiExecutor;
|
||||
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.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
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 roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class ConversationActivity extends RoboActivity
|
||||
implements EventListener, OnClickListener, OnItemClickListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConversationActivity.class.getName());
|
||||
|
||||
private String contactName = null;
|
||||
private ConversationAdapter adapter = null;
|
||||
private ListView list = null;
|
||||
private ListLoadingProgressBar loading = null;
|
||||
private ImageButton composeButton = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
private volatile ContactId contactId = null;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile AuthorId localAuthorId = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
int id = i.getIntExtra("org.briarproject.CONTACT_ID", -1);
|
||||
if(id == -1) throw new IllegalStateException();
|
||||
contactId = new ContactId(id);
|
||||
contactName = i.getStringExtra("org.briarproject.CONTACT_NAME");
|
||||
if(contactName == null) throw new IllegalStateException();
|
||||
setTitle(contactName);
|
||||
byte[] b = i.getByteArrayExtra("org.briarproject.GROUP_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
b = i.getByteArrayExtra("org.briarproject.LOCAL_AUTHOR_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
localAuthorId = new AuthorId(b);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
layout.setOrientation(VERTICAL);
|
||||
layout.setGravity(CENTER_HORIZONTAL);
|
||||
|
||||
adapter = new ConversationAdapter(this);
|
||||
list = new ListView(this);
|
||||
// Give me all the width and all the unused height
|
||||
list.setLayoutParams(MATCH_WRAP_1);
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(this);
|
||||
layout.addView(list);
|
||||
|
||||
// Show a progress bar while the list is loading
|
||||
list.setVisibility(GONE);
|
||||
loading = new ListLoadingProgressBar(this);
|
||||
layout.addView(loading);
|
||||
|
||||
layout.addView(new HorizontalBorder(this));
|
||||
|
||||
composeButton = new ImageButton(this);
|
||||
composeButton.setBackgroundResource(0);
|
||||
composeButton.setImageResource(R.drawable.content_new_email);
|
||||
composeButton.setEnabled(false); // Enabled after loading the headers
|
||||
composeButton.setOnClickListener(this);
|
||||
layout.addView(composeButton);
|
||||
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
db.addListener(this);
|
||||
loadHeaders();
|
||||
}
|
||||
|
||||
private void loadHeaders() {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<MessageHeader> headers =
|
||||
db.getInboxMessageHeaders(contactId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayHeaders(headers);
|
||||
} catch(NoSuchContactException e) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
} 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 displayHeaders(final Collection<MessageHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
list.setVisibility(VISIBLE);
|
||||
loading.setVisibility(GONE);
|
||||
composeButton.setEnabled(true);
|
||||
adapter.clear();
|
||||
for(MessageHeader h : headers)
|
||||
adapter.add(new ConversationItem(h));
|
||||
adapter.sort(ConversationItemComparator.INSTANCE);
|
||||
adapter.notifyDataSetChanged();
|
||||
selectFirstUnread();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void selectFirstUnread() {
|
||||
int firstUnread = -1, count = adapter.getCount();
|
||||
for(int i = 0; i < count; i++) {
|
||||
if(!adapter.getItem(i).getHeader().isRead()) {
|
||||
firstUnread = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(firstUnread == -1) list.setSelection(count - 1);
|
||||
else list.setSelection(firstUnread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
if(result == ReadPrivateMessageActivity.RESULT_PREV) {
|
||||
int position = request - 1;
|
||||
if(position >= 0 && position < adapter.getCount())
|
||||
displayMessage(position);
|
||||
} else if(result == ReadPrivateMessageActivity.RESULT_NEXT) {
|
||||
int position = request + 1;
|
||||
if(position >= 0 && position < adapter.getCount())
|
||||
displayMessage(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
db.removeListener(this);
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if(e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if(c.getContactId().equals(contactId)) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if(e instanceof MessageAddedEvent) {
|
||||
GroupId g = ((MessageAddedEvent) e).getGroup().getId();
|
||||
if(g.equals(groupId)) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
} else if(e instanceof MessageExpiredEvent) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(this, WritePrivateMessageActivity.class);
|
||||
i.putExtra("org.briarproject.CONTACT_NAME", contactName);
|
||||
i.putExtra("org.briarproject.GROUP_ID", groupId.getBytes());
|
||||
i.putExtra("org.briarproject.LOCAL_AUTHOR_ID", localAuthorId.getBytes());
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
displayMessage(position);
|
||||
}
|
||||
|
||||
private void displayMessage(int position) {
|
||||
MessageHeader header = adapter.getItem(position).getHeader();
|
||||
Intent i = new Intent(this, ReadPrivateMessageActivity.class);
|
||||
i.putExtra("org.briarproject.CONTACT_ID", contactId.getInt());
|
||||
i.putExtra("org.briarproject.CONTACT_NAME", contactName);
|
||||
i.putExtra("org.briarproject.GROUP_ID", groupId.getBytes());
|
||||
i.putExtra("org.briarproject.LOCAL_AUTHOR_ID", localAuthorId.getBytes());
|
||||
i.putExtra("org.briarproject.AUTHOR_NAME", header.getAuthor().getName());
|
||||
i.putExtra("org.briarproject.MESSAGE_ID", header.getId().getBytes());
|
||||
i.putExtra("org.briarproject.CONTENT_TYPE", header.getContentType());
|
||||
i.putExtra("org.briarproject.TIMESTAMP", header.getTimestamp());
|
||||
startActivityForResult(i, position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static java.text.DateFormat.SHORT;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.db.MessageHeader;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
class ConversationAdapter extends ArrayAdapter<ConversationItem> {
|
||||
|
||||
ConversationAdapter(Context ctx) {
|
||||
super(ctx, android.R.layout.simple_expandable_list_item_1,
|
||||
new ArrayList<ConversationItem>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
MessageHeader header = getItem(position).getHeader();
|
||||
Context ctx = getContext();
|
||||
|
||||
LinearLayout layout = new LinearLayout(ctx);
|
||||
layout.setOrientation(HORIZONTAL);
|
||||
if(!header.isRead()) {
|
||||
Resources res = ctx.getResources();
|
||||
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(10, 10, 10, 10);
|
||||
name.setText(header.getAuthor().getName());
|
||||
layout.addView(name);
|
||||
|
||||
TextView date = new TextView(ctx);
|
||||
date.setTextSize(14);
|
||||
date.setPadding(0, 10, 10, 10);
|
||||
long then = header.getTimestamp(), now = System.currentTimeMillis();
|
||||
date.setText(DateUtils.formatSameDayTime(then, now, SHORT, SHORT));
|
||||
layout.addView(date);
|
||||
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.db.MessageHeader;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ConversationItem {
|
||||
|
||||
private final MessageHeader header;
|
||||
private boolean expanded;
|
||||
private byte[] body;
|
||||
|
||||
ConversationItem(MessageHeader header) {
|
||||
this.header = header;
|
||||
expanded = false;
|
||||
body = null;
|
||||
}
|
||||
|
||||
MessageHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
boolean isExpanded() {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
void setExpanded(boolean expanded) {
|
||||
this.expanded = expanded;
|
||||
}
|
||||
|
||||
byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class ConversationItemComparator
|
||||
implements Comparator<ConversationItem> {
|
||||
|
||||
public static final ConversationItemComparator INSTANCE =
|
||||
new ConversationItemComparator();
|
||||
|
||||
public int compare(ConversationItem a, ConversationItem b) {
|
||||
// The oldest message comes first
|
||||
long aTime = a.getHeader().getTimestamp();
|
||||
long bTime = b.getHeader().getTimestamp();
|
||||
if(aTime < bTime) return -1;
|
||||
if(aTime > bTime) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import static android.view.Gravity.CENTER;
|
||||
import static android.view.Gravity.CENTER_VERTICAL;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static java.text.DateFormat.SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.HorizontalBorder;
|
||||
import org.briarproject.android.util.HorizontalSpace;
|
||||
import org.briarproject.api.AuthorId;
|
||||
import org.briarproject.api.android.DatabaseUiExecutor;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
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 roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ReadPrivateMessageActivity extends RoboActivity
|
||||
implements OnClickListener {
|
||||
|
||||
static final int RESULT_REPLY = RESULT_FIRST_USER;
|
||||
static final int RESULT_PREV = RESULT_FIRST_USER + 1;
|
||||
static final int RESULT_NEXT = RESULT_FIRST_USER + 2;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ReadPrivateMessageActivity.class.getName());
|
||||
|
||||
private String contactName = null;
|
||||
private AuthorId localAuthorId = null;
|
||||
private boolean read = false;
|
||||
private ImageButton readButton = null, prevButton = null, nextButton = null;
|
||||
private ImageButton replyButton = null;
|
||||
private TextView content = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
private volatile MessageId messageId = null;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile long timestamp = -1;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
contactName = i.getStringExtra("org.briarproject.CONTACT_NAME");
|
||||
if(contactName == null) throw new IllegalStateException();
|
||||
setTitle(contactName);
|
||||
byte[] b = i.getByteArrayExtra("org.briarproject.LOCAL_AUTHOR_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
localAuthorId = new AuthorId(b);
|
||||
String authorName = i.getStringExtra("org.briarproject.AUTHOR_NAME");
|
||||
if(authorName == null) throw new IllegalStateException();
|
||||
b = i.getByteArrayExtra("org.briarproject.MESSAGE_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
messageId = new MessageId(b);
|
||||
b = i.getByteArrayExtra("org.briarproject.GROUP_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
String contentType = i.getStringExtra("org.briarproject.CONTENT_TYPE");
|
||||
if(contentType == null) throw new IllegalStateException();
|
||||
timestamp = i.getLongExtra("org.briarproject.TIMESTAMP", -1);
|
||||
if(timestamp == -1) throw new IllegalStateException();
|
||||
|
||||
if(state == null) {
|
||||
read = false;
|
||||
setReadInDatabase(true);
|
||||
} else {
|
||||
read = state.getBoolean("org.briarproject.READ");
|
||||
}
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_WRAP);
|
||||
layout.setOrientation(VERTICAL);
|
||||
|
||||
ScrollView scrollView = new ScrollView(this);
|
||||
// Give me all the width and all the unused height
|
||||
scrollView.setLayoutParams(MATCH_WRAP_1);
|
||||
|
||||
LinearLayout message = new LinearLayout(this);
|
||||
message.setOrientation(VERTICAL);
|
||||
Resources res = getResources();
|
||||
message.setBackgroundColor(res.getColor(R.color.content_background));
|
||||
|
||||
LinearLayout header = new LinearLayout(this);
|
||||
header.setLayoutParams(MATCH_WRAP);
|
||||
header.setOrientation(HORIZONTAL);
|
||||
header.setGravity(CENTER_VERTICAL);
|
||||
|
||||
TextView name = new TextView(this);
|
||||
// Give me all the unused width
|
||||
name.setLayoutParams(WRAP_WRAP_1);
|
||||
name.setTextSize(18);
|
||||
name.setMaxLines(1);
|
||||
name.setPadding(10, 10, 10, 10);
|
||||
name.setText(authorName);
|
||||
header.addView(name);
|
||||
|
||||
TextView date = new TextView(this);
|
||||
date.setTextSize(14);
|
||||
date.setPadding(0, 10, 10, 10);
|
||||
long now = System.currentTimeMillis();
|
||||
date.setText(DateUtils.formatSameDayTime(timestamp, now, SHORT, SHORT));
|
||||
header.addView(date);
|
||||
message.addView(header);
|
||||
|
||||
if(contentType.equals("text/plain")) {
|
||||
// Load and display the message body
|
||||
content = new TextView(this);
|
||||
content.setPadding(10, 0, 10, 10);
|
||||
message.addView(content);
|
||||
loadMessageBody();
|
||||
}
|
||||
scrollView.addView(message);
|
||||
layout.addView(scrollView);
|
||||
|
||||
layout.addView(new HorizontalBorder(this));
|
||||
|
||||
LinearLayout footer = new LinearLayout(this);
|
||||
footer.setLayoutParams(MATCH_WRAP);
|
||||
footer.setOrientation(HORIZONTAL);
|
||||
footer.setGravity(CENTER);
|
||||
|
||||
readButton = new ImageButton(this);
|
||||
readButton.setBackgroundResource(0);
|
||||
if(read) readButton.setImageResource(R.drawable.content_unread);
|
||||
else readButton.setImageResource(R.drawable.content_read);
|
||||
readButton.setOnClickListener(this);
|
||||
footer.addView(readButton);
|
||||
footer.addView(new HorizontalSpace(this));
|
||||
|
||||
prevButton = new ImageButton(this);
|
||||
prevButton.setBackgroundResource(0);
|
||||
prevButton.setImageResource(R.drawable.navigation_previous_item);
|
||||
prevButton.setOnClickListener(this);
|
||||
footer.addView(prevButton);
|
||||
footer.addView(new HorizontalSpace(this));
|
||||
|
||||
nextButton = new ImageButton(this);
|
||||
nextButton.setBackgroundResource(0);
|
||||
nextButton.setImageResource(R.drawable.navigation_next_item);
|
||||
nextButton.setOnClickListener(this);
|
||||
footer.addView(nextButton);
|
||||
footer.addView(new HorizontalSpace(this));
|
||||
|
||||
replyButton = new ImageButton(this);
|
||||
replyButton.setBackgroundResource(0);
|
||||
replyButton.setImageResource(R.drawable.social_reply);
|
||||
replyButton.setOnClickListener(this);
|
||||
footer.addView(replyButton);
|
||||
layout.addView(footer);
|
||||
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
private void setReadInDatabase(final boolean read) {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
db.setReadFlag(messageId, read);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Setting flag took " + duration + " ms");
|
||||
setReadInUi(read);
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Interrupted while waiting for database");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setReadInUi(final boolean read) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
ReadPrivateMessageActivity.this.read = read;
|
||||
if(read) readButton.setImageResource(R.drawable.content_unread);
|
||||
else readButton.setImageResource(R.drawable.content_read);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadMessageBody() {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
byte[] body = db.getMessageBody(messageId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Loading message took " + duration + " ms");
|
||||
final String text = new String(body, "UTF-8");
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
content.setText(text);
|
||||
}
|
||||
});
|
||||
} catch(NoSuchMessageException e) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Message removed");
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
} 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();
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
state.putBoolean("org.briarproject.READ", read);
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
if(view == readButton) {
|
||||
setReadInDatabase(!read);
|
||||
} else if(view == prevButton) {
|
||||
setResult(RESULT_PREV);
|
||||
finish();
|
||||
} else if(view == nextButton) {
|
||||
setResult(RESULT_NEXT);
|
||||
finish();
|
||||
} else if(view == replyButton) {
|
||||
Intent i = new Intent(this, WritePrivateMessageActivity.class);
|
||||
i.putExtra("org.briarproject.CONTACT_NAME", contactName);
|
||||
i.putExtra("org.briarproject.GROUP_ID", groupId.getBytes());
|
||||
i.putExtra("org.briarproject.LOCAL_AUTHOR_ID",
|
||||
localAuthorId.getBytes());
|
||||
i.putExtra("org.briarproject.PARENT_ID", messageId.getBytes());
|
||||
i.putExtra("org.briarproject.TIMESTAMP", timestamp);
|
||||
startActivity(i);
|
||||
setResult(RESULT_REPLY);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.Contact;
|
||||
import org.briarproject.api.ContactId;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
public class SelectContactsDialog extends DialogFragment
|
||||
implements DialogInterface.OnMultiChoiceClickListener {
|
||||
|
||||
private final Set<ContactId> selected = new HashSet<ContactId>();
|
||||
|
||||
private Listener listener = null;
|
||||
private Contact[] contacts = null;
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setContacts(Collection<Contact> contacts) {
|
||||
this.contacts = contacts.toArray(new Contact[contacts.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle state) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
String[] names = new String[contacts.length];
|
||||
for(int i = 0; i < contacts.length; i++)
|
||||
names[i] = contacts[i].getAuthor().getName();
|
||||
builder.setMultiChoiceItems(names, new boolean[contacts.length], this);
|
||||
builder.setPositiveButton(R.string.done_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
listener.contactsSelected(selected);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
listener.contactSelectionCancelled();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
|
||||
if(isChecked) selected.add(contacts[which].getId());
|
||||
else selected.remove(contacts[which].getId());
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void contactsSelected(Collection<ContactId> selected);
|
||||
|
||||
void contactSelectionCancelled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import static android.text.InputType.TYPE_CLASS_TEXT;
|
||||
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
|
||||
import static android.view.Gravity.CENTER_VERTICAL;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.HorizontalSpace;
|
||||
import org.briarproject.api.AuthorId;
|
||||
import org.briarproject.api.LocalAuthor;
|
||||
import org.briarproject.api.android.DatabaseUiExecutor;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.db.NoSuchSubscriptionException;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
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 roboguice.activity.RoboActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class WritePrivateMessageActivity extends RoboActivity
|
||||
implements OnClickListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(WritePrivateMessageActivity.class.getName());
|
||||
|
||||
private TextView from = null, to = null;
|
||||
private ImageButton sendButton = null;
|
||||
private EditText content = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject private volatile DatabaseComponent db;
|
||||
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
|
||||
@Inject private volatile LifecycleManager lifecycleManager;
|
||||
@Inject @CryptoExecutor private volatile Executor cryptoExecutor;
|
||||
@Inject private volatile MessageFactory messageFactory;
|
||||
private volatile String contactName = null;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile AuthorId localAuthorId = null;
|
||||
private volatile MessageId parentId = null;
|
||||
private volatile long timestamp = -1;
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile Group group = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
contactName = i.getStringExtra("org.briarproject.CONTACT_NAME");
|
||||
if(contactName == null) throw new IllegalStateException();
|
||||
byte[] b = i.getByteArrayExtra("org.briarproject.GROUP_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
b = i.getByteArrayExtra("org.briarproject.LOCAL_AUTHOR_ID");
|
||||
if(b == null) throw new IllegalStateException();
|
||||
localAuthorId = new AuthorId(b);
|
||||
b = i.getByteArrayExtra("org.briarproject.PARENT_ID");
|
||||
if(b != null) parentId = new MessageId(b);
|
||||
timestamp = i.getLongExtra("org.briarproject.TIMESTAMP", -1);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_WRAP);
|
||||
layout.setOrientation(VERTICAL);
|
||||
|
||||
LinearLayout header = new LinearLayout(this);
|
||||
header.setLayoutParams(MATCH_WRAP);
|
||||
header.setOrientation(HORIZONTAL);
|
||||
header.setGravity(CENTER_VERTICAL);
|
||||
|
||||
from = new TextView(this);
|
||||
from.setTextSize(18);
|
||||
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 group
|
||||
sendButton.setOnClickListener(this);
|
||||
header.addView(sendButton);
|
||||
layout.addView(header);
|
||||
|
||||
to = new TextView(this);
|
||||
to.setTextSize(18);
|
||||
to.setPadding(10, 0, 10, 10);
|
||||
String format = getResources().getString(R.string.format_to);
|
||||
to.setText(String.format(format, contactName));
|
||||
layout.addView(to);
|
||||
|
||||
content = new EditText(this);
|
||||
content.setId(1);
|
||||
int inputType = TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
| TYPE_TEXT_FLAG_CAP_SENTENCES;
|
||||
content.setInputType(inputType);
|
||||
layout.addView(content);
|
||||
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadAuthorAndGroup();
|
||||
}
|
||||
|
||||
private void loadAuthorAndGroup() {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
localAuthor = db.getLocalAuthor(localAuthorId);
|
||||
group = db.getGroup(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayLocalAuthor();
|
||||
} catch(NoSuchContactException e) {
|
||||
finish();
|
||||
} catch(NoSuchSubscriptionException e) {
|
||||
finish();
|
||||
} catch(DbException e) {
|
||||
if(LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} catch(InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for database");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayLocalAuthor() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Resources res = getResources();
|
||||
String format = res.getString(R.string.format_from);
|
||||
String name = localAuthor.getName();
|
||||
from.setText(String.format(format, name));
|
||||
sendButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Toast.makeText(this, R.string.message_sent_toast, LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void createMessage(final byte[] body) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
// Don't use an earlier timestamp than the parent
|
||||
long time = System.currentTimeMillis();
|
||||
time = Math.max(time, timestamp + 1);
|
||||
Message m;
|
||||
try {
|
||||
m = messageFactory.createAnonymousMessage(parentId, group,
|
||||
"text/plain", time, body);
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
storeMessage(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMessage(final Message m) {
|
||||
dbUiExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
lifecycleManager.waitForDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
db.addLocalMessage(m);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info("Storing message took " + duration + " ms");
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user