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 a052e7393423b772b928f17bf217c9b2b6c50b96..7a9652a610f22ff4a994655458cdfbf33ff6cf3d 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 e78e1c63980169f266bef00394d1232bdf245bd4..0cdc34ba86a400842a912bc59e6367b32c6095df 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"/> + <TextView + android:id="@+id/introductionNotPossibleView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_activity_horizontal" + android:text="@string/introduction_not_possible" + android:textSize="@dimen/text_size_large" + android:visibility="gone" + tools:visibility="visible"/> + <org.briarproject.briar.android.view.LargeTextInputView android:id="@+id/introductionMessageView" android:layout_width="match_parent" android:layout_height="wrap_content" + android:visibility="gone" app:buttonText="@string/introduction_button" app:hint="@string/introduction_message_hint" - app:maxLines="5"/> + app:maxLines="5" + tools:visibility="visible"/> </LinearLayout> </ScrollView> \ 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 d4b40f5bb8636cadbb67b1b4fe315222e0fbe11c..38b04cd534ba1ac46342e839d5f61b8e7d90514b 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -144,6 +144,7 @@ <string name="introduction_onboarding_title">Introduce your contacts</string> <string name="introduction_onboarding_text">You can introduce your contacts to each other, so they don\'t need to meet in person to connect on Briar.</string> <string name="introduction_activity_title">Select Contact</string> + <string name="introduction_not_possible">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.</string> <string name="introduction_message_title">Introduce Contacts</string> <string name="introduction_message_hint">Add a message (optional)</string> <string name="introduction_button">Make Introduction</string> 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 15936e8ad84f9fad746902d0ac88293a01b0495f..813b039de32f72b1e9ade352e11853ac02fb977b 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 0e4639f7fbafbb460a4997fbf7069d4e4827974c..bfb81a4b8d738e75536c45426154812d41a9622c 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 6d13b80f197a56bb9ee04f3920a2bf53355afbad..880210039adb938baefdba797482b1358373d40e 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);