diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/WrongQrCodeTypeException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/WrongQrCodeTypeException.java new file mode 100644 index 000000000..070d1e3c0 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/WrongQrCodeTypeException.java @@ -0,0 +1,25 @@ +package org.briarproject.bramble.api.qrcode; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; +import org.briarproject.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +/** + * Thrown when a QR code that has been scanned does not have the expected type. + */ +@Immutable +@NotNullByDefault +public class WrongQrCodeTypeException extends FormatException { + + private final QrCodeType qrCodeType; + + public WrongQrCodeTypeException(QrCodeType qrCodeType) { + this.qrCodeType = qrCodeType; + } + + public QrCodeType getQrCodeType() { + return qrCodeType; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java index f4d81665d..d41cffa99 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java @@ -14,6 +14,7 @@ import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.qrcode.QrCodeClassifier; import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; +import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException; import org.briarproject.nullsafety.NotNullByDefault; import java.io.ByteArrayInputStream; @@ -49,7 +50,8 @@ class PayloadParserImpl implements PayloadParser { public Payload parse(String payloadString) throws IOException { Pair typeAndVersion = qrCodeClassifier.classifyQrCode(payloadString); - if (typeAndVersion.getFirst() != BQP) throw new FormatException(); + QrCodeType qrCodeType = typeAndVersion.getFirst(); + if (qrCodeType != BQP) throw new WrongQrCodeTypeException(qrCodeType); int formatVersion = typeAndVersion.getSecond(); if (formatVersion != QR_FORMAT_VERSION) { boolean tooOld = formatVersion < QR_FORMAT_VERSION; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java index 02f1cbdeb..a9be2c458 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.qrcode.QrCodeClassifier; import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; +import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException; import org.briarproject.bramble.test.BrambleMockTestCase; import org.jmock.Expectations; import org.junit.Test; @@ -40,8 +41,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase { private final PayloadParserImpl payloadParser = new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier); - @Test(expected = FormatException.class) - public void testThrowsFormatExceptionForWrongQrCodeType() throws Exception { + @Test(expected = WrongQrCodeTypeException.class) + public void testThrowsExceptionForWrongQrCodeType() throws Exception { expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION); payloadParser.parse(payload); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java index df56be6bb..e2eaca306 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java @@ -3,12 +3,14 @@ package org.briarproject.briar.android.contact.add.nearby; import android.graphics.Bitmap; import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import androidx.annotation.Nullable; abstract class AddContactState { static class KeyAgreementListening extends AddContactState { + final Bitmap qrCode; KeyAgreementListening(Bitmap qrCode) { @@ -29,6 +31,7 @@ abstract class AddContactState { } static class ContactExchangeFinished extends AddContactState { + final ContactExchangeResult result; ContactExchangeFinished(ContactExchangeResult result) { @@ -37,25 +40,30 @@ abstract class AddContactState { } static class Failed extends AddContactState { - /** - * Non-null if failed due to the scanned QR code version. - * True if the app producing the code is too old. - * False if the scanning app is too old. - */ - @Nullable - final Boolean qrCodeTooOld; - Failed(@Nullable Boolean qrCodeTooOld) { - this.qrCodeTooOld = qrCodeTooOld; + static class WrongQrCodeType extends Failed { + + final QrCodeType qrCodeType; + + WrongQrCodeType(QrCodeType qrCodeType) { + this.qrCodeType = qrCodeType; + } } - Failed() { - this(null); + static class WrongQrCodeVersion extends Failed { + + final boolean qrCodeTooOld; + + WrongQrCodeVersion(boolean qrCodeTooOld) { + this.qrCodeTooOld = qrCodeTooOld; + } } } abstract static class ContactExchangeResult { + static class Success extends ContactExchangeResult { + final Author remoteAuthor; Success(Author remoteAuthor) { @@ -64,6 +72,7 @@ abstract class AddContactState { } static class Error extends ContactExchangeResult { + @Nullable final Author duplicateAuthor; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java index d58524e30..eaadaea0c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java @@ -6,12 +6,15 @@ import android.view.MenuItem; import android.widget.Toast; import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult; import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion; import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; @@ -34,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; @@ -141,9 +145,15 @@ public class AddNearbyContactActivity extends BriarActivity ContactExchangeResult result = ((ContactExchangeFinished) state).result; onContactExchangeResult(result); + } else if (state instanceof WrongQrCodeType) { + QrCodeType qrCodeType = ((WrongQrCodeType) state).qrCodeType; + if (qrCodeType == MAILBOX) onMailboxQrCodeScanned(); + else showErrorFragment(); + } else if (state instanceof WrongQrCodeVersion) { + boolean qrCodeTooOld = ((WrongQrCodeVersion) state).qrCodeTooOld; + onWrongQrCodeVersion(qrCodeTooOld); } else if (state instanceof Failed) { - Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; - onAddingContactFailed(qrCodeTooOld); + showErrorFragment(); } } @@ -170,15 +180,20 @@ public class AddNearbyContactActivity extends BriarActivity } else throw new AssertionError(); } - private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) { - if (qrCodeTooOld == null) { - showErrorFragment(); - } else { - String msg; - if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1); - else msg = getString(R.string.qr_code_too_new_1); - showNextFragment(AddNearbyContactErrorFragment.newInstance(msg)); - } + private void onMailboxQrCodeScanned() { + String title = getString(R.string.qr_code_invalid); + String msg = getString(R.string.mailbox_qr_code_for_contact); + showNextFragment( + AddNearbyContactErrorFragment.newInstance(title, msg, false)); + } + + private void onWrongQrCodeVersion(boolean qrCodeTooOld) { + String title = getString(R.string.qr_code_invalid); + String msg; + if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1); + else msg = getString(R.string.qr_code_too_new_1); + showNextFragment( + AddNearbyContactErrorFragment.newInstance(title, msg, false)); } private void showErrorFragment() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java index 408fab4ec..129906695 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java @@ -22,6 +22,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.view.View.GONE; import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; @@ -31,17 +32,22 @@ public class AddNearbyContactErrorFragment extends BaseFragment { public static final String TAG = AddNearbyContactErrorFragment.class.getName(); - private static final String ERROR_MSG = "errorMessage"; + private static final String ARG_TITLE = "title"; + private static final String ARG_ERROR_MSG = "message"; + private static final String ARG_FEEDBACK = "feedback"; @Inject ViewModelProvider.Factory viewModelFactory; private AddNearbyContactViewModel viewModel; - public static AddNearbyContactErrorFragment newInstance(String errorMsg) { + public static AddNearbyContactErrorFragment newInstance(String title, + String errorMessage, boolean feedback) { AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment(); Bundle args = new Bundle(); - args.putString(ERROR_MSG, errorMsg); + args.putString(ARG_TITLE, title); + args.putString(ARG_ERROR_MSG, errorMessage); + args.putBoolean(ARG_FEEDBACK, feedback); f.setArguments(args); return f; } @@ -66,19 +72,32 @@ public class AddNearbyContactErrorFragment extends BaseFragment { View v = inflater.inflate(R.layout.fragment_error_contact_exchange, container, false); - // set optional error message - TextView explanation = v.findViewById(R.id.errorMessage); + String title = null, errorMessage = null; + boolean feedback = true; Bundle args = getArguments(); - String errorMessage = args == null ? null : args.getString(ERROR_MSG); - if (errorMessage == null) { - explanation.setText(getString(R.string.add_contact_error_two_way)); - } else { - explanation.setText(args.getString(ERROR_MSG)); + if (args != null) { + title = args.getString(ARG_TITLE); + errorMessage = args.getString(ARG_ERROR_MSG); + feedback = args.getBoolean(ARG_FEEDBACK, true); + } + + if (title != null) { + TextView titleView = v.findViewById(R.id.errorTitle); + titleView.setText(title); + } + + if (errorMessage != null) { + TextView messageView = v.findViewById(R.id.errorMessage); + messageView.setText(errorMessage); } - // make feedback link clickable TextView sendFeedback = v.findViewById(R.id.sendFeedback); - onSingleLinkClick(sendFeedback, this::triggerFeedback); + if (feedback) { + // make feedback link clickable + onSingleLinkClick(sendFeedback, this::triggerFeedback); + } else { + sendFeedback.setVisibility(GONE); + } // buttons Button tryAgain = v.findViewById(R.id.tryAgainButton); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java index 375dfee2a..de8148bd6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java @@ -43,6 +43,7 @@ import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.event.TransportStateEvent; +import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin; import org.briarproject.briar.R; @@ -50,9 +51,13 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Contact import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Success; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeStarted; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.QrCodeScanned; import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeUtils; import org.briarproject.briar.android.viewmodel.LiveEvent; @@ -373,11 +378,11 @@ class AddNearbyContactViewModel extends AndroidViewModel } else if (e instanceof KeyAgreementAbortedEvent) { LOG.info("KeyAgreementAbortedEvent received"); resetPayloadFlags(); - state.setValue(new AddContactState.Failed()); + state.setValue(new Failed()); } else if (e instanceof KeyAgreementFailedEvent) { LOG.info("KeyAgreementFailedEvent received"); resetPayloadFlags(); - state.setValue(new AddContactState.Failed()); + state.setValue(new Failed()); } } @@ -446,16 +451,19 @@ class AddNearbyContactViewModel extends AndroidViewModel Payload remotePayload = payloadParser.parse(result.getText()); gotRemotePayload = true; currentTask.connectAndRunProtocol(remotePayload); - state.postValue(new AddContactState.QrCodeScanned()); + state.postValue(new QrCodeScanned()); + } catch (WrongQrCodeTypeException e) { + resetPayloadFlags(); + state.postValue(new WrongQrCodeType(e.getQrCodeType())); } catch (UnsupportedVersionException e) { resetPayloadFlags(); - state.postValue(new AddContactState.Failed(e.isTooOld())); + state.postValue(new WrongQrCodeVersion(e.isTooOld())); } catch (IOException | IllegalArgumentException e) { LOG.log(WARNING, "QR Code Invalid", e); androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(), R.string.qr_code_invalid, LENGTH_LONG).show()); resetPayloadFlags(); - state.postValue(new AddContactState.Failed()); + state.postValue(new Failed()); } } diff --git a/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml b/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml index 40cc0983a..ce11de39c 100644 --- a/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml +++ b/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml @@ -51,12 +51,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_xlarge" + android:text="@string/add_contact_error_two_way" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" app:layout_constraintBottom_toTopOf="@+id/sendFeedback" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/errorTitle" - tools:text="error explanation" /> + app:layout_constraintTop_toBottomOf="@+id/errorTitle" /> The QR code is invalid The QR code you have scanned comes from an older version of Briar.\n\nPlease ask your contact to upgrade to the latest version and then try again. The QR code you have scanned comes from a newer version of Briar.\n\nPlease upgrade to the latest version and then try again. + The QR code you have scanned comes from Briar Mailbox.\n\nIf you want to link a Mailbox, please choose Settings > Mailbox from the Briar menu. Camera error Connecting to device\u2026 Authenticating with device\u2026