From 920a1d0431dc990e2dce0ba7a1d0adb06ba29e10 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 10 Aug 2018 16:53:01 -0300 Subject: [PATCH 1/5] Show an error fragment when contact exchange fails --- .../android/activity/ActivityComponent.java | 3 + .../keyagreement/ContactExchangeActivity.java | 25 ++-- .../ContactExchangeErrorFragment.java | 118 ++++++++++++++++++ .../keyagreement/KeyAgreementActivity.java | 10 +- .../fragment_error_contact_exchange.xml | 105 ++++++++++++++++ briar-android/src/main/res/values/strings.xml | 2 + 6 files changed, 246 insertions(+), 17 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java create mode 100644 briar-android/src/main/res/layout/fragment_error_contact_exchange.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 8a8abb9f8..c327d64ae 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -27,6 +27,7 @@ import org.briarproject.briar.android.introduction.ContactChooserFragment; import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionMessageFragment; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; +import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment; import org.briarproject.briar.android.keyagreement.IntroFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; @@ -208,4 +209,6 @@ public interface ActivityComponent { void inject(SettingsFragment fragment); void inject(ScreenFilterDialogFragment fragment); + + void inject(ContactExchangeErrorFragment fragment); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java index e053c50c1..c80e96f6a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java @@ -14,7 +14,6 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.R.string; import org.briarproject.briar.android.activity.ActivityComponent; import java.util.logging.Logger; @@ -48,7 +47,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); - getSupportActionBar().setTitle(string.add_contact_title); + getSupportActionBar().setTitle(R.string.add_contact_title); } private void startContactExchange(KeyAgreementResult result) { @@ -75,7 +74,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements public void contactExchangeSucceeded(Author remoteAuthor) { runOnUiThreadUnlessDestroyed(() -> { String contactName = remoteAuthor.getName(); - String format = getString(string.contact_added_toast); + String format = getString(R.string.contact_added_toast); String text = String.format(format, contactName); Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG) .show(); @@ -87,7 +86,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements public void duplicateContact(Author remoteAuthor) { runOnUiThreadUnlessDestroyed(() -> { String contactName = remoteAuthor.getName(); - String format = getString(string.contact_already_exists); + String format = getString(R.string.contact_already_exists); String text = String.format(format, contactName); Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG) .show(); @@ -98,18 +97,14 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements @Override public void contactExchangeFailed() { runOnUiThreadUnlessDestroyed(() -> { - Toast.makeText(ContactExchangeActivity.this, - string.contact_exchange_failed, LENGTH_LONG).show(); - finish(); + showErrorFragment(R.string.contact_exchange_failed); }); } @UiThread @Override public void keyAgreementFailed() { - // TODO show failure somewhere persistent? - Toast.makeText(this, R.string.connection_failed, - LENGTH_LONG).show(); + showErrorFragment(R.string.connection_failed); } @UiThread @@ -124,14 +119,12 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements return getString(R.string.authenticating_with_device); } + @Nullable @UiThread @Override public String keyAgreementAborted(boolean remoteAborted) { - // TODO show abort somewhere persistent? - Toast.makeText(this, - remoteAborted ? R.string.connection_aborted_remote : - R.string.connection_aborted_local, LENGTH_LONG) - .show(); + showErrorFragment(remoteAborted ? R.string.connection_aborted_remote : + R.string.connection_aborted_local); return null; } @@ -139,6 +132,6 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements @Override public String keyAgreementFinished(KeyAgreementResult result) { startContactExchange(result); - return getString(string.exchanging_contact_details); + return getString(R.string.exchanging_contact_details); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java new file mode 100644 index 000000000..5dc6ddc2e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java @@ -0,0 +1,118 @@ +package org.briarproject.briar.android.keyagreement; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.SpannableStringBuilder; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.acra.ACRA; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.fragment.BaseFragment; +import org.briarproject.briar.android.util.UserFeedback; + +import javax.inject.Inject; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ContactExchangeErrorFragment extends BaseFragment { + + public static final String TAG = + ContactExchangeErrorFragment.class.getName(); + private static final String ERROR_MSG = "errorMessage"; + + public static ContactExchangeErrorFragment newInstance(String message) { + ContactExchangeErrorFragment f = new ContactExchangeErrorFragment(); + Bundle args = new Bundle(); + args.putString(ERROR_MSG, message); + f.setArguments(args); + return f; + } + + @Inject + AndroidExecutor androidExecutor; + + private String errorMessage; + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args == null) { + throw new IllegalArgumentException("Use newInstance()"); + } + errorMessage = args.getString(ERROR_MSG); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View v = inflater + .inflate(R.layout.fragment_error_contact_exchange, container, + false); + + // make feedback link clickable + TextView explanation = v.findViewById(R.id.errorMessage); + SpannableStringBuilder ssb = + new SpannableStringBuilder(explanation.getText()); + ClickableSpan[] spans = + ssb.getSpans(0, ssb.length(), ClickableSpan.class); + if (spans.length != 1) throw new AssertionError(); + ClickableSpan span = spans[0]; + int start = ssb.getSpanStart(span); + int end = ssb.getSpanEnd(span); + ssb.removeSpan(span); + ClickableSpan cSpan = new ClickableSpan() { + @Override + public void onClick(View v) { + triggerFeedback(); + } + }; + ssb.setSpan(cSpan, start + 1, end, 0); + explanation.setText(ssb); + explanation.setMovementMethod(new LinkMovementMethod()); + + // technical error message + TextView msg = v.findViewById(R.id.errorMessageTech); + msg.setText(errorMessage); + + // buttons + Button tryAgain = v.findViewById(R.id.tryAgainButton); + tryAgain.setOnClickListener(view -> { + if (getActivity() != null) getActivity().onBackPressed(); + }); + Button cancel = v.findViewById(R.id.cancelButton); + cancel.setOnClickListener(view -> finish()); + return v; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + private void triggerFeedback() { + finish(); + androidExecutor.runOnBackgroundThread( + () -> ACRA.getErrorReporter() + .handleException(new UserFeedback(), false)); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java index 189ca1771..8caedc130 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java @@ -7,6 +7,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; +import android.support.annotation.StringRes; import android.support.annotation.UiThread; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; @@ -28,6 +29,7 @@ import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.keyagreement.IntroFragment.IntroScreenSeenListener; +import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener; import org.briarproject.briar.android.util.UiUtils; import java.util.logging.Logger; @@ -50,7 +52,7 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMI @ParametersNotNullByDefault public abstract class KeyAgreementActivity extends BriarActivity implements BaseFragmentListener, IntroScreenSeenListener, - KeyAgreementFragment.KeyAgreementEventListener { + KeyAgreementEventListener { private enum BluetoothState { UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED @@ -185,6 +187,12 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } } + protected void showErrorFragment(@StringRes int errorResId) { + String errorMessage = getString(errorResId); + BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMessage); + showNextFragment(f); + } + private boolean checkPermissions() { if (ContextCompat.checkSelfPermission(this, CAMERA) != PERMISSION_GRANTED) { 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 new file mode 100644 index 000000000..55ee4074e --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_error_contact_exchange.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + +