mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Implement conversation data persistence
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
<string name="contact_list_button">Contacts</string>
|
||||
<string name="delete_contact">Delete contact</string>
|
||||
<string name="contact_deleted_toast">Contact deleted</string>
|
||||
<string name="contact_deletion_failed_toast">Failed to delete contact</string>
|
||||
<string name="forums_button">Forums</string>
|
||||
<string name="settings_button">Settings</string>
|
||||
<string name="sign_out_button">Sign Out</string>
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.briarproject.android.contact.ConversationController;
|
||||
import org.briarproject.android.contact.ConversationControllerImpl;
|
||||
import org.briarproject.android.controller.BriarController;
|
||||
import org.briarproject.android.controller.BriarControllerImpl;
|
||||
import org.briarproject.android.controller.ConfigController;
|
||||
@@ -91,6 +93,14 @@ public class ActivityModule {
|
||||
return dbController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected ConversationController provideConversationController(
|
||||
ConversationControllerImpl conversationController) {
|
||||
activity.addLifecycleController(conversationController);
|
||||
return conversationController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected ForumController provideForumController(
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.briarproject.CoreModule;
|
||||
import org.briarproject.android.api.AndroidExecutor;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.api.ReferenceManager;
|
||||
import org.briarproject.android.contact.ConversationPersistentData;
|
||||
import org.briarproject.android.forum.ForumPersistentData;
|
||||
import org.briarproject.android.report.BriarReportSender;
|
||||
import org.briarproject.api.contact.ContactExchangeTask;
|
||||
@@ -113,6 +114,8 @@ public interface AndroidComponent extends CoreEagerSingletons {
|
||||
|
||||
AndroidExecutor androidExecutor();
|
||||
|
||||
ConversationPersistentData conversationPersistentData();
|
||||
|
||||
ForumPersistentData forumPersistentData();
|
||||
|
||||
@IoExecutor
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.api.ReferenceManager;
|
||||
import org.briarproject.android.contact.ConversationPersistentData;
|
||||
import org.briarproject.android.forum.ForumPersistentData;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
@@ -138,6 +139,12 @@ public class AppModule {
|
||||
return notificationManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ConversationPersistentData provideConversationPersistence() {
|
||||
return new ConversationPersistentData();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumPersistentData provideForumPersistence() {
|
||||
|
||||
@@ -27,30 +27,12 @@ import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.conversation.ConversationItem;
|
||||
import org.briarproject.api.conversation.ConversationItem.IncomingItem;
|
||||
import org.briarproject.api.conversation.ConversationManager;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.event.ContactConnectedEvent;
|
||||
import org.briarproject.api.event.ContactDisconnectedEvent;
|
||||
import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.ConversationItemReceivedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.MessagesAckedEvent;
|
||||
import org.briarproject.api.event.MessagesSentEvent;
|
||||
import org.briarproject.api.messaging.PrivateMessage;
|
||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.api.plugins.ConnectionRegistry;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
@@ -61,7 +43,6 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -72,10 +53,9 @@ import im.delight.android.identicons.IdenticonDrawable;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class ConversationActivity extends BriarActivity
|
||||
implements EventListener, OnClickListener,
|
||||
implements ConversationController.ConversationListener, OnClickListener,
|
||||
ConversationAdapter.ConversationHandler,
|
||||
ConversationAdapter.MessageUpdatedHandler {
|
||||
|
||||
@@ -85,11 +65,6 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
@Inject
|
||||
ConnectionRegistry connectionRegistry;
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
protected Executor cryptoExecutor;
|
||||
|
||||
private ConversationAdapter adapter;
|
||||
private CircleImageView toolbarAvatar;
|
||||
@@ -101,19 +76,9 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile ConversationManager conversationManager;
|
||||
@Inject
|
||||
protected volatile EventBus eventBus;
|
||||
@Inject
|
||||
volatile PrivateMessageFactory privateMessageFactory;
|
||||
protected volatile ConversationController conversationController;
|
||||
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile ContactId contactId = null;
|
||||
private volatile String contactName = null;
|
||||
private volatile byte[] contactIdenticonKey = null;
|
||||
private volatile boolean connected = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
@@ -161,6 +126,22 @@ public class ConversationActivity extends BriarActivity
|
||||
sendButton.setEnabled(false);
|
||||
sendButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
conversationController
|
||||
.loadConversation(groupId, new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
displayContactDetails();
|
||||
// Load the messages here to make sure we have a
|
||||
// contactId
|
||||
loadMessages();
|
||||
} else {
|
||||
// TODO Maybe an error dialog ?
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,16 +164,13 @@ public class ConversationActivity extends BriarActivity
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
eventBus.addListener(this);
|
||||
notificationManager.blockNotification(groupId);
|
||||
notificationManager.clearPrivateMessageNotification(groupId);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
eventBus.removeListener(this);
|
||||
notificationManager.unblockNotification(groupId);
|
||||
if (isFinishing()) markMessagesRead();
|
||||
}
|
||||
@@ -203,8 +181,16 @@ public class ConversationActivity extends BriarActivity
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.conversation_actions, menu);
|
||||
|
||||
hideIntroductionActionWhenOneContact(
|
||||
menu.findItem(R.id.action_introduction));
|
||||
final MenuItem introduction = menu.findItem(R.id.action_introduction);
|
||||
conversationController.shouldHideIntroductionAction(
|
||||
new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
introduction.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -217,6 +203,7 @@ public class ConversationActivity extends BriarActivity
|
||||
onBackPressed();
|
||||
return true;
|
||||
case R.id.action_introduction:
|
||||
ContactId contactId = conversationController.getContactId();
|
||||
if (contactId == null) return false;
|
||||
Intent intent = new Intent(this, IntroductionActivity.class);
|
||||
intent.putExtra(IntroductionActivity.CONTACT_ID,
|
||||
@@ -242,46 +229,18 @@ public class ConversationActivity extends BriarActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
if (contactId == null)
|
||||
contactId = conversationManager.getContactId(groupId);
|
||||
if (contactName == null || contactIdenticonKey == null) {
|
||||
Contact contact = contactManager.getContact(contactId);
|
||||
contactName = contact.getAuthor().getName();
|
||||
contactIdenticonKey =
|
||||
contact.getAuthor().getId().getBytes();
|
||||
}
|
||||
connected = connectionRegistry.isConnected(contactId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading contact took " + duration + " ms");
|
||||
displayContactDetails();
|
||||
// Load the messages here to make sure we have a contactId
|
||||
loadMessages();
|
||||
} catch (NoSuchContactException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be called after the conversation has been loaded.
|
||||
*/
|
||||
private void displayContactDetails() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
toolbarAvatar.setImageDrawable(
|
||||
new IdenticonDrawable(contactIdenticonKey));
|
||||
toolbarTitle.setText(contactName);
|
||||
toolbarAvatar.setImageDrawable(new IdenticonDrawable(
|
||||
conversationController.getContactIdenticonKey()));
|
||||
toolbarTitle.setText(conversationController.getContactName());
|
||||
|
||||
if (connected) {
|
||||
if (conversationController.isConnected()) {
|
||||
toolbarStatus.setImageDrawable(ContextCompat
|
||||
.getDrawable(ConversationActivity.this,
|
||||
R.drawable.contact_online));
|
||||
@@ -294,48 +253,30 @@ public class ConversationActivity extends BriarActivity
|
||||
toolbarStatus
|
||||
.setContentDescription(getString(R.string.offline));
|
||||
}
|
||||
adapter.setContactName(contactName);
|
||||
adapter.setContactName(conversationController.getContactName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadMessages() {
|
||||
runOnDbThread(new Runnable() {
|
||||
conversationController.loadMessages(new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
if (contactId == null)
|
||||
contactId = conversationManager.getContactId(groupId);
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
List<ConversationItem> items =
|
||||
conversationManager.getMessages(contactId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
displayMessages(items);
|
||||
} catch (NoSuchContactException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayMessages(final List<ConversationItem> items) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendButton.setEnabled(true);
|
||||
if (items.isEmpty()) {
|
||||
// we have no messages,
|
||||
// so let the list know to hide progress bar
|
||||
list.showData();
|
||||
conversationController.getConversationItems();
|
||||
sendButton.setEnabled(true);
|
||||
if (items.isEmpty()) {
|
||||
// we have no messages,
|
||||
// so let the list know to hide progress bar
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.addAll(items);
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
} else {
|
||||
adapter.addAll(items);
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -362,71 +303,14 @@ public class ConversationActivity extends BriarActivity
|
||||
if (unread.isEmpty()) return;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking " + unread.size() + " messages read");
|
||||
markMessagesRead(Collections.unmodifiableList(unread));
|
||||
}
|
||||
|
||||
private void markMessagesRead(final Collection<ConversationItem> unread) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
for (ConversationItem item : unread)
|
||||
conversationManager.setReadFlag(contactId, item, true);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact removed");
|
||||
finishOnUiThread();
|
||||
}
|
||||
} else if (e instanceof ConversationItemReceivedEvent) {
|
||||
ConversationItemReceivedEvent event =
|
||||
(ConversationItemReceivedEvent) e;
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
LOG.info("Message received, adding");
|
||||
addConversationItem(event.getItem());
|
||||
markMessageReadIfNew(event.getItem());
|
||||
}
|
||||
} else if (e instanceof MessagesSentEvent) {
|
||||
MessagesSentEvent m = (MessagesSentEvent) e;
|
||||
if (m.getContactId().equals(contactId)) {
|
||||
LOG.info("Messages sent");
|
||||
markMessages(m.getMessageIds(), true, false);
|
||||
}
|
||||
} else if (e instanceof MessagesAckedEvent) {
|
||||
MessagesAckedEvent m = (MessagesAckedEvent) e;
|
||||
if (m.getContactId().equals(contactId)) {
|
||||
LOG.info("Messages acked");
|
||||
markMessages(m.getMessageIds(), true, true);
|
||||
}
|
||||
} else if (e instanceof ContactConnectedEvent) {
|
||||
ContactConnectedEvent c = (ContactConnectedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact connected");
|
||||
connected = true;
|
||||
displayContactDetails();
|
||||
}
|
||||
} else if (e instanceof ContactDisconnectedEvent) {
|
||||
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact disconnected");
|
||||
connected = false;
|
||||
displayContactDetails();
|
||||
}
|
||||
}
|
||||
conversationController.markMessagesRead(
|
||||
Collections.unmodifiableList(unread),
|
||||
new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
// TODO something?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markMessageReadIfNew(final ConversationItem item) {
|
||||
@@ -438,32 +322,19 @@ public class ConversationActivity extends BriarActivity
|
||||
// Mark the message read if it's the newest message
|
||||
long lastMsgTime = last.getTime();
|
||||
long newMsgTime = item.getTime();
|
||||
if (newMsgTime > lastMsgTime) markNewMessageRead(item);
|
||||
else loadMessages();
|
||||
if (newMsgTime > lastMsgTime)
|
||||
conversationController.markNewMessageRead(item);
|
||||
} else {
|
||||
// mark the message as read as well if it is the first one
|
||||
markNewMessageRead(item);
|
||||
conversationController.markNewMessageRead(item);
|
||||
}
|
||||
loadMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markNewMessageRead(final ConversationItem item) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
conversationManager.setReadFlag(contactId, item, true);
|
||||
loadMessages();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markMessages(final Collection<MessageId> messageIds,
|
||||
@Override
|
||||
public void markMessages(final Collection<MessageId> messageIds,
|
||||
final boolean sent, final boolean seen) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -483,6 +354,12 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ConversationItem item) {
|
||||
addConversationItem(item);
|
||||
markMessageReadIfNew(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
markMessagesRead();
|
||||
@@ -501,37 +378,14 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
|
||||
private void createMessage(final byte[] body, final long timestamp) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
storeMessage(privateMessageFactory
|
||||
.createPrivateMessage(groupId, timestamp, null,
|
||||
"text/plain", body), body);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMessage(final PrivateMessage m, final byte[] body) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
ConversationItem item = conversationManager.addLocalMessage(m, body);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Storing message took " + duration + " ms");
|
||||
addConversationItem(item);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
conversationController.createMessage(body, timestamp,
|
||||
new UiResultHandler<ConversationItem>(this) {
|
||||
@Override
|
||||
public void onResultUi(ConversationItem item) {
|
||||
if (item != null)
|
||||
addConversationItem(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void askToRemoveContact() {
|
||||
@@ -553,82 +407,42 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
|
||||
private void removeContact() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// make sure contactId is initialised
|
||||
if (contactId == null)
|
||||
contactId = conversationManager.getContactId(groupId);
|
||||
// remove contact with that ID
|
||||
contactManager.removeContact(contactId);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
finishAfterContactRemoved();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void finishAfterContactRemoved() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String deleted = getString(R.string.contact_deleted_toast);
|
||||
Toast.makeText(ConversationActivity.this, deleted, LENGTH_SHORT)
|
||||
.show();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideIntroductionActionWhenOneContact(final MenuItem item) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (contactManager.getActiveContacts().size() < 2) {
|
||||
hideIntroductionAction(item);
|
||||
conversationController
|
||||
.removeContact(new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
String deleted =
|
||||
getString(R.string.contact_deleted_toast);
|
||||
Toast.makeText(ConversationActivity.this, deleted,
|
||||
LENGTH_SHORT)
|
||||
.show();
|
||||
finish();
|
||||
} else {
|
||||
String failed = getString(
|
||||
R.string.contact_deletion_failed_toast);
|
||||
Toast.makeText(ConversationActivity.this, failed,
|
||||
LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideIntroductionAction(final MenuItem item) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
item.setVisible(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondToItem(final ConversationItem item,
|
||||
final boolean accept) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||
try {
|
||||
conversationManager
|
||||
.respondToItem(contactId, item, accept, timestamp);
|
||||
loadMessages();
|
||||
} catch (DbException | FormatException e) {
|
||||
// TODO decide how to make this type-agnostic
|
||||
introductionResponseError();
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
public void respondToItem(ConversationItem item, boolean accept) {
|
||||
long minTimestamp = getMinTimestampForNewMessage();
|
||||
conversationController.respondToItem(item, accept, minTimestamp,
|
||||
new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
loadMessages();
|
||||
} else {
|
||||
// TODO decide how to make this type-agnostic
|
||||
introductionResponseError();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void introductionResponseError() {
|
||||
@@ -653,4 +467,9 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactUpdated() {
|
||||
displayContactDetails();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.conversation.ConversationItem;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface ConversationController extends ActivityLifecycleController {
|
||||
|
||||
void loadConversation(GroupId groupId,
|
||||
UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
void loadMessages(UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
void createMessage(byte[] body, long timestamp,
|
||||
UiResultHandler<ConversationItem> resultHandler);
|
||||
|
||||
ContactId getContactId();
|
||||
|
||||
String getContactName();
|
||||
|
||||
byte[] getContactIdenticonKey();
|
||||
|
||||
List<ConversationItem> getConversationItems();
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
void markMessagesRead(Collection<ConversationItem> unread,
|
||||
UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
void markNewMessageRead(ConversationItem item);
|
||||
|
||||
void removeContact(UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
void respondToItem(ConversationItem item, boolean accept, long minTimestamp,
|
||||
UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
void shouldHideIntroductionAction(UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
interface ConversationListener {
|
||||
void contactUpdated();
|
||||
|
||||
void markMessages(Collection<MessageId> messageIds, boolean sent,
|
||||
boolean seen);
|
||||
|
||||
void messageReceived(ConversationItem item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.conversation.ConversationItem;
|
||||
import org.briarproject.api.conversation.ConversationManager;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.event.ContactConnectedEvent;
|
||||
import org.briarproject.api.event.ContactDisconnectedEvent;
|
||||
import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.ConversationItemReceivedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.MessagesAckedEvent;
|
||||
import org.briarproject.api.event.MessagesSentEvent;
|
||||
import org.briarproject.api.messaging.PrivateMessage;
|
||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.api.plugins.ConnectionRegistry;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class ConversationControllerImpl extends DbControllerImpl
|
||||
implements ConversationController, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConversationControllerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected Activity activity;
|
||||
@Inject
|
||||
protected ConnectionRegistry connectionRegistry;
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
protected Executor cryptoExecutor;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile ConversationManager conversationManager;
|
||||
@Inject
|
||||
protected volatile EventBus eventBus;
|
||||
@Inject
|
||||
protected volatile PrivateMessageFactory privateMessageFactory;
|
||||
@Inject
|
||||
protected ConversationPersistentData data;
|
||||
|
||||
private ConversationListener listener;
|
||||
|
||||
@Inject
|
||||
ConversationControllerImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreate() {
|
||||
if (activity instanceof ConversationListener) {
|
||||
listener = (ConversationListener) activity;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"An activity that injects the ConversationController " +
|
||||
"must implement the ConversationListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
if (activity.isFinishing()) {
|
||||
data.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConversation(final GroupId groupId,
|
||||
final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (data.getGroupId() == null ||
|
||||
!data.getGroupId().equals(groupId)) {
|
||||
data.setGroupId(groupId);
|
||||
long now = System.currentTimeMillis();
|
||||
ContactId contactId =
|
||||
conversationManager.getContactId(groupId);
|
||||
data.setContact(contactManager.getContact(contactId));
|
||||
data.setConnected(
|
||||
connectionRegistry.isConnected(contactId));
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(
|
||||
"Loading contact took " + duration + " ms");
|
||||
}
|
||||
resultHandler.onResult(true);
|
||||
} catch (NoSuchContactException e) {
|
||||
resultHandler.onResult(false);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessages(final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (getContactId() != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
data.addConversationItems(
|
||||
conversationManager
|
||||
.getMessages(getContactId()));
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(
|
||||
"Loading headers took " + duration + " ms");
|
||||
}
|
||||
resultHandler.onResult(true);
|
||||
} catch (NoSuchContactException e) {
|
||||
resultHandler.onResult(false);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createMessage(final byte[] body, final long timestamp,
|
||||
final UiResultHandler<ConversationItem> resultHandler) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PrivateMessage m = privateMessageFactory
|
||||
.createPrivateMessage(data.getGroupId(), timestamp,
|
||||
null, "text/plain", body);
|
||||
storeMessage(m, body, resultHandler);
|
||||
} catch (FormatException e) {
|
||||
// TODO why was this being thrown?
|
||||
//throw new RuntimeException(e);
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMessage(final PrivateMessage m, final byte[] body,
|
||||
final UiResultHandler<ConversationItem> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
ConversationItem item =
|
||||
conversationManager.addLocalMessage(m, body);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Storing message took " + duration + " ms");
|
||||
resultHandler.onResult(item);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId getContactId() {
|
||||
return data.getContact() == null ? null : data.getContact().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContactName() {
|
||||
return data.getContact() == null ? null :
|
||||
data.getContact().getAuthor().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContactIdenticonKey() {
|
||||
return data.getContact() == null ? null :
|
||||
data.getContact().getAuthor().getId().getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConversationItem> getConversationItems() {
|
||||
return data.getConversationItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return data.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markMessagesRead(final Collection<ConversationItem> unread,
|
||||
final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (getContactId() != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
for (ConversationItem item : unread)
|
||||
conversationManager
|
||||
.setReadFlag(getContactId(), item, true);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
}
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markNewMessageRead(final ConversationItem item) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (getContactId() != null) {
|
||||
conversationManager
|
||||
.setReadFlag(getContactId(), item, true);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeContact(final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (getContactId() != null) {
|
||||
contactManager.removeContact(getContactId());
|
||||
}
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondToItem(final ConversationItem item, final boolean accept,
|
||||
final long minTimestamp,
|
||||
final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, minTimestamp);
|
||||
try {
|
||||
conversationManager
|
||||
.respondToItem(getContactId(), item, accept,
|
||||
timestamp);
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException | FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldHideIntroductionAction(
|
||||
final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
resultHandler.onResult(
|
||||
contactManager.getActiveContacts().size() < 2);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(getContactId())) {
|
||||
LOG.info("Contact removed");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (e instanceof ConversationItemReceivedEvent) {
|
||||
final ConversationItemReceivedEvent event =
|
||||
(ConversationItemReceivedEvent) e;
|
||||
if (event.getContactId().equals(getContactId())) {
|
||||
LOG.info("Message received, adding");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.messageReceived(event.getItem());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (e instanceof MessagesSentEvent) {
|
||||
final MessagesSentEvent m = (MessagesSentEvent) e;
|
||||
if (m.getContactId().equals(getContactId())) {
|
||||
LOG.info("Messages sent");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.markMessages(m.getMessageIds(), true, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (e instanceof MessagesAckedEvent) {
|
||||
final MessagesAckedEvent m = (MessagesAckedEvent) e;
|
||||
if (m.getContactId().equals(getContactId())) {
|
||||
LOG.info("Messages acked");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.markMessages(m.getMessageIds(), true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (e instanceof ContactConnectedEvent) {
|
||||
ContactConnectedEvent c = (ContactConnectedEvent) e;
|
||||
if (c.getContactId().equals(getContactId())) {
|
||||
LOG.info("Contact connected");
|
||||
data.setConnected(true);
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.contactUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (e instanceof ContactDisconnectedEvent) {
|
||||
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
|
||||
if (c.getContactId().equals(getContactId())) {
|
||||
LOG.info("Contact disconnected");
|
||||
data.setConnected(false);
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.contactUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.conversation.ConversationItem;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* This class is a singleton that defines the data that should persist, i.e.
|
||||
* still be present in memory after activity restarts. This class is not thread
|
||||
* safe.
|
||||
*/
|
||||
public class ConversationPersistentData {
|
||||
|
||||
private volatile GroupId groupId;
|
||||
private volatile Contact contact;
|
||||
private volatile boolean connected;
|
||||
private volatile List<ConversationItem> items = new ArrayList<>();
|
||||
|
||||
public void clearAll() {
|
||||
groupId = null;
|
||||
contact = null;
|
||||
connected = false;
|
||||
items.clear();
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(GroupId groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public void setContact(Contact contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
public void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
public void addConversationItems(Collection<ConversationItem> items) {
|
||||
this.items.addAll(items);
|
||||
}
|
||||
|
||||
public List<ConversationItem> getConversationItems() {
|
||||
return Collections.unmodifiableList(items);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user