diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java index a052e7393..7a9652a61 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java @@ -2,6 +2,7 @@ package org.briarproject.briar.android.introduction; import android.content.Context; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; import android.view.LayoutInflater; import android.view.MenuItem; @@ -32,6 +33,7 @@ import im.delight.android.identicons.IdenticonDrawable; import static android.app.Activity.RESULT_OK; import static android.view.View.GONE; +import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.WARNING; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; @@ -124,14 +126,15 @@ public class IntroductionMessageFragment extends BaseFragment new ContactId(contactId1)); Contact c2 = contactManager.getContact( new ContactId(contactId2)); - setUpViews(c1, c2); + boolean possible = introductionManager.canIntroduce(c1, c2); + setUpViews(c1, c2, possible); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } }); } - private void setUpViews(Contact c1, Contact c2) { + private void setUpViews(Contact c1, Contact c2, boolean possible) { introductionActivity.runOnUiThreadUnlessDestroyed(() -> { contact1 = c1; contact2 = c2; @@ -146,13 +149,22 @@ public class IntroductionMessageFragment extends BaseFragment ui.contactName1.setText(c1.getAuthor().getName()); ui.contactName2.setText(c2.getAuthor().getName()); - // set button action - ui.message.setListener(IntroductionMessageFragment.this); - - // hide progress bar and show views + // hide progress bar ui.progressBar.setVisibility(GONE); - ui.message.setSendButtonEnabled(true); - ui.message.showSoftKeyboard(); + + if (possible) { + // set button action + ui.message.setListener(IntroductionMessageFragment.this); + + // show views + ui.notPossible.setVisibility(GONE); + ui.message.setVisibility(VISIBLE); + ui.message.setSendButtonEnabled(true); + ui.message.showSoftKeyboard(); + } else { + ui.notPossible.setVisibility(VISIBLE); + ui.message.setVisibility(GONE); + } }); } @@ -174,7 +186,8 @@ public class IntroductionMessageFragment extends BaseFragment ui.message.setSendButtonEnabled(false); String msg = ui.message.getText().toString(); - msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH); + if (msg.equals("")) msg = null; + else msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH); makeIntroduction(contact1, contact2, msg); // don't wait for the introduction to be made before finishing activity @@ -183,7 +196,8 @@ public class IntroductionMessageFragment extends BaseFragment introductionActivity.supportFinishAfterTransition(); } - private void makeIntroduction(Contact c1, Contact c2, String msg) { + private void makeIntroduction(Contact c1, Contact c2, + @Nullable String msg) { introductionActivity.runOnDbThread(() -> { // actually make the introduction try { @@ -207,6 +221,7 @@ public class IntroductionMessageFragment extends BaseFragment private final ProgressBar progressBar; private final CircleImageView avatar1, avatar2; private final TextView contactName1, contactName2; + private final TextView notPossible; private final TextInputView message; private ViewHolder(View v) { @@ -215,6 +230,7 @@ public class IntroductionMessageFragment extends BaseFragment avatar2 = v.findViewById(R.id.avatarContact2); contactName1 = v.findViewById(R.id.nameContact1); contactName2 = v.findViewById(R.id.nameContact2); + notPossible = v.findViewById(R.id.introductionNotPossibleView); message = v.findViewById(R.id.introductionMessageView); } } diff --git a/briar-android/src/main/res/layout/introduction_message.xml b/briar-android/src/main/res/layout/introduction_message.xml index e78e1c639..0cdc34ba8 100644 --- a/briar-android/src/main/res/layout/introduction_message.xml +++ b/briar-android/src/main/res/layout/introduction_message.xml @@ -94,13 +94,25 @@ android:layout_gravity="center" tools:visibility="gone"/> + + + app:maxLines="5" + tools:visibility="visible"/> \ No newline at end of file diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index d4b40f5bb..38b04cd53 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -144,6 +144,7 @@ Introduce your contacts You can introduce your contacts to each other, so they don\'t need to meet in person to connect on Briar. Select Contact + You already have one introduction in progress with these contacts. Please allow for this to finish first. If you or your contacts are rarely online, this can take some time. Introduce Contacts Add a message (optional) Make Introduction diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java index 15936e8ad..813b039de 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java @@ -25,6 +25,8 @@ public interface IntroductionManager extends ConversationClient { */ int CLIENT_VERSION = 1; + boolean canIntroduce(Contact c1, Contact c2) throws DbException; + /** * Sends two initial introduction messages. */ diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 0e4639f7f..bfb81a4b8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -47,6 +47,7 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroducerState.START; import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ACCEPT; @@ -267,6 +268,28 @@ class IntroductionManagerImpl extends ConversationClientImpl } } + @Override + public boolean canIntroduce(Contact c1, Contact c2) throws DbException { + Transaction txn = db.startTransaction(true); + try { + // Look up the session, if there is one + Author introducer = identityManager.getLocalAuthor(txn); + SessionId sessionId = + crypto.getSessionId(introducer, c1.getAuthor(), + c2.getAuthor()); + StoredSession ss = getSession(txn, sessionId); + if (ss == null) return true; + IntroducerSession session = + sessionParser.parseIntroducerSession(ss.bdfSession); + if (session.getState() == START) return true; + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + return false; + } + @Override public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, long timestamp) throws DbException { @@ -398,12 +421,11 @@ class IntroductionManagerImpl extends ConversationClientImpl Role role = sessionParser.getRole(bdfSession); SessionId sessionId; Author author; - LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); if (role == INTRODUCER) { IntroducerSession session = sessionParser.parseIntroducerSession(bdfSession); sessionId = session.getSessionId(); - if (localAuthor.equals(session.getIntroducee1().author)) { + if (contactGroupId.equals(session.getIntroducee1().groupId)) { author = session.getIntroducee2().author; } else { author = session.getIntroducee1().author; @@ -419,6 +441,7 @@ class IntroductionManagerImpl extends ConversationClientImpl if (msg == null || body == null) throw new AssertionError(); RequestMessage rm = messageParser.parseRequestMessage(msg, body); String message = rm.getMessage(); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); boolean contactExists = contactManager .contactExists(txn, rm.getAuthor().getId(), localAuthor.getId()); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 6d13b80f1..880210039 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -23,6 +23,7 @@ import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.TestDatabaseModule; +import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionMessage; @@ -446,6 +447,26 @@ public class IntroductionIntegrationTest assertFalse(listener2.aborted); } + @Test(expected = ProtocolStateException.class) + public void testDoubleIntroduction() throws Exception { + // we can make an introduction + assertTrue(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // no more introduction allowed while the existing one is in progress + assertFalse(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + + // try it anyway and fail + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + } + @Test public void testIntroducerRemovedCleanup() throws Exception { addListeners(true, true);