diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index d44bc389a..aebe567c6 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -36,6 +36,7 @@
Contacts
Delete contact
Contact deleted
+ Failed to delete contact
Forums
Settings
Sign Out
diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java
index ea984bca0..502da98fb 100644
--- a/briar-android/src/org/briarproject/android/ActivityModule.java
+++ b/briar-android/src/org/briarproject/android/ActivityModule.java
@@ -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(
diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java
index d5b9a8393..25054d1ab 100644
--- a/briar-android/src/org/briarproject/android/AndroidComponent.java
+++ b/briar-android/src/org/briarproject/android/AndroidComponent.java
@@ -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
diff --git a/briar-android/src/org/briarproject/android/AppModule.java b/briar-android/src/org/briarproject/android/AppModule.java
index 36933a118..f79a5b90d 100644
--- a/briar-android/src/org/briarproject/android/AppModule.java
+++ b/briar-android/src/org/briarproject/android/AppModule.java
@@ -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() {
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index dd630c368..c0eacc944 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -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(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(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(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 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 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 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(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 messageIds,
+ @Override
+ public void markMessages(final Collection 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(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(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(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();
+ }
}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationController.java b/briar-android/src/org/briarproject/android/contact/ConversationController.java
new file mode 100644
index 000000000..5afd594ec
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contact/ConversationController.java
@@ -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 resultHandler);
+
+ void loadMessages(UiResultHandler resultHandler);
+
+ void createMessage(byte[] body, long timestamp,
+ UiResultHandler resultHandler);
+
+ ContactId getContactId();
+
+ String getContactName();
+
+ byte[] getContactIdenticonKey();
+
+ List getConversationItems();
+
+ boolean isConnected();
+
+ void markMessagesRead(Collection unread,
+ UiResultHandler resultHandler);
+
+ void markNewMessageRead(ConversationItem item);
+
+ void removeContact(UiResultHandler resultHandler);
+
+ void respondToItem(ConversationItem item, boolean accept, long minTimestamp,
+ UiResultHandler resultHandler);
+
+ void shouldHideIntroductionAction(UiResultHandler resultHandler);
+
+ interface ConversationListener {
+ void contactUpdated();
+
+ void markMessages(Collection messageIds, boolean sent,
+ boolean seen);
+
+ void messageReceived(ConversationItem item);
+ }
+}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationControllerImpl.java b/briar-android/src/org/briarproject/android/contact/ConversationControllerImpl.java
new file mode 100644
index 000000000..b6b8311d3
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contact/ConversationControllerImpl.java
@@ -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 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 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 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 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 getConversationItems() {
+ return data.getConversationItems();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return data.isConnected();
+ }
+
+ @Override
+ public void markMessagesRead(final Collection unread,
+ final UiResultHandler 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 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 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 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();
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationPersistentData.java b/briar-android/src/org/briarproject/android/contact/ConversationPersistentData.java
new file mode 100644
index 000000000..0ef4e1b07
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contact/ConversationPersistentData.java
@@ -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 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 items) {
+ this.items.addAll(items);
+ }
+
+ public List getConversationItems() {
+ return Collections.unmodifiableList(items);
+ }
+}