From 6d1f1c785205201450802aefe3043a8ef34009ec Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 1 Feb 2021 15:22:50 -0300 Subject: [PATCH] Get rid of KeyAgreementEventListener and communicate via ViewModel --- .../add/nearby/ContactExchangeActivity.java | 102 +++++----------- .../add/nearby/ContactExchangeViewModel.java | 64 +++++++++- .../add/nearby/KeyAgreementActivity.java | 4 +- .../add/nearby/KeyAgreementFragment.java | 110 +++++------------- 4 files changed, 125 insertions(+), 155 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java index 8c031b375..7d2a6e1e3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java @@ -4,17 +4,17 @@ import android.os.Bundle; import android.widget.Toast; import org.briarproject.bramble.api.identity.Author; -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.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState; import javax.annotation.Nullable; -import androidx.annotation.UiThread; - import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.ABORTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FAILED; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -25,77 +25,39 @@ public class ContactExchangeActivity extends KeyAgreementActivity { super.onCreate(state); requireNonNull(getSupportActionBar()) .setTitle(R.string.add_contact_title); + viewModel.getKeyAgreementState() + .observe(this, this::onKeyAgreementStateChanged); + viewModel.getContactExchangeResult() + .observe(this, this::onContactExchangeResult); } - private void startContactExchange(KeyAgreementResult agreementResult) { - viewModel.getContactExchangeResult().observe(this, result -> { - if (result instanceof ContactExchangeResult.Success) { - Author remoteAuthor = - ((ContactExchangeResult.Success) result).remoteAuthor; - contactExchangeSucceeded(remoteAuthor); - } else if (result instanceof ContactExchangeResult.Error) { - Author duplicateAuthor = - ((ContactExchangeResult.Error) result).duplicateAuthor; - if (duplicateAuthor == null) contactExchangeFailed(); - else duplicateContact(duplicateAuthor); - } else throw new AssertionError(); - }); - viewModel.startContactExchange(agreementResult.getTransportId(), - agreementResult.getConnection(), agreementResult.getMasterKey(), - agreementResult.wasAlice()); + private void onKeyAgreementStateChanged(KeyAgreementState state) { + if (state == ABORTED || state == FAILED) { + showErrorFragment(); + } } - @UiThread - private void contactExchangeSucceeded(Author remoteAuthor) { - String contactName = remoteAuthor.getName(); - String text = getString(R.string.contact_added_toast, contactName); - Toast.makeText(this, text, LENGTH_LONG).show(); - supportFinishAfterTransition(); - } - - @UiThread - private void duplicateContact(Author remoteAuthor) { - String contactName = remoteAuthor.getName(); - String format = getString(R.string.contact_already_exists); - String text = String.format(format, contactName); - Toast.makeText(this, text, LENGTH_LONG).show(); - finish(); - } - - @UiThread - private void contactExchangeFailed() { - showErrorFragment(); - } - - @UiThread - @Override - public void keyAgreementFailed() { - showErrorFragment(); - } - - @UiThread - @Override - public String keyAgreementWaiting() { - return getString(R.string.waiting_for_contact_to_scan); - } - - @UiThread - @Override - public String keyAgreementStarted() { - return getString(R.string.authenticating_with_device); - } - - @UiThread - @Override - public void keyAgreementAborted(boolean remoteAborted) { - showErrorFragment(); - } - - @UiThread - @Override - public String keyAgreementFinished(KeyAgreementResult result) { - startContactExchange(result); - return getString(R.string.exchanging_contact_details); + private void onContactExchangeResult(ContactExchangeResult result) { + if (result instanceof ContactExchangeResult.Success) { + Author remoteAuthor = + ((ContactExchangeResult.Success) result).remoteAuthor; + String contactName = remoteAuthor.getName(); + String text = getString(R.string.contact_added_toast, contactName); + Toast.makeText(this, text, LENGTH_LONG).show(); + supportFinishAfterTransition(); + } else if (result instanceof ContactExchangeResult.Error) { + Author duplicateAuthor = + ((ContactExchangeResult.Error) result).duplicateAuthor; + if (duplicateAuthor == null) { + showErrorFragment(); + } else { + String contactName = duplicateAuthor.getName(); + String text = + getString(R.string.contact_already_exists, contactName); + Toast.makeText(this, text, LENGTH_LONG).show(); + supportFinishAfterTransition(); + } + } else throw new AssertionError(); } private void showErrorFragment() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java index 1f4665548..19394e5fe 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java @@ -8,6 +8,15 @@ import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; @@ -29,32 +38,75 @@ import androidx.lifecycle.MutableLiveData; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.ABORTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FAILED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FINISHED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.STARTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.WAITING; @NotNullByDefault -class ContactExchangeViewModel extends AndroidViewModel { +class ContactExchangeViewModel extends AndroidViewModel + implements EventListener { private static final Logger LOG = getLogger(ContactExchangeViewModel.class.getName()); + enum KeyAgreementState { + WAITING, STARTED, FINISHED, ABORTED, FAILED + } + + private final EventBus eventBus; private final Executor ioExecutor; private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; + private final MutableLiveData keyAgreementState = + new MutableLiveData<>(); private final MutableLiveData exchangeResult = new MutableLiveData<>(); @Inject - ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor, + ContactExchangeViewModel(Application app, + EventBus eventBus, + @IoExecutor Executor ioExecutor, ContactExchangeManager contactExchangeManager, ConnectionManager connectionManager) { super(app); + this.eventBus = eventBus; this.ioExecutor = ioExecutor; this.contactExchangeManager = contactExchangeManager; this.connectionManager = connectionManager; } + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof KeyAgreementWaitingEvent) { + keyAgreementState.setValue(WAITING); + } else if (e instanceof KeyAgreementStartedEvent) { + keyAgreementState.setValue(STARTED); + } else if (e instanceof KeyAgreementAbortedEvent) { + keyAgreementState.setValue(ABORTED); + } else if (e instanceof KeyAgreementFinishedEvent) { + keyAgreementState.setValue(FINISHED); + KeyAgreementResult result = + ((KeyAgreementFinishedEvent) e).getResult(); + startContactExchange(result); + } else if (e instanceof KeyAgreementFailedEvent) { + keyAgreementState.setValue(FAILED); + } + } + @UiThread - void startContactExchange(TransportId t, DuplexTransportConnection conn, - SecretKey masterKey, boolean alice) { + private void startContactExchange(KeyAgreementResult result) { + TransportId t = result.getTransportId(); + DuplexTransportConnection conn = result.getConnection(); + SecretKey masterKey = result.getMasterKey(); + boolean alice = result.wasAlice(); ioExecutor.execute(() -> { try { Contact contact = contactExchangeManager.exchangeContacts(conn, @@ -83,6 +135,10 @@ class ContactExchangeViewModel extends AndroidViewModel { } } + LiveData getKeyAgreementState() { + return keyAgreementState; + } + LiveData getContactExchangeResult() { return exchangeResult; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java index ca58bff16..6a35128e7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java @@ -23,7 +23,6 @@ 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.IntroFragment.IntroScreenSeenListener; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment.KeyAgreementEventListener; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; @@ -61,8 +60,7 @@ import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListene @MethodsNotNullByDefault @ParametersNotNullByDefault public abstract class KeyAgreementActivity extends BriarActivity implements - BaseFragmentListener, IntroScreenSeenListener, - KeyAgreementEventListener, EventListener { + BaseFragmentListener, IntroScreenSeenListener, EventListener { private enum BluetoothDecision { /** diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java index c8eb8e03b..34a29281c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java @@ -17,23 +17,17 @@ import com.google.zxing.Result; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadParser; -import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent; -import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent; -import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; -import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent; -import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState; import org.briarproject.briar.android.fragment.BaseEventFragment; import org.briarproject.briar.android.view.QrCodeView; @@ -47,6 +41,8 @@ import javax.inject.Inject; import javax.inject.Provider; import androidx.annotation.UiThread; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ViewModelProvider; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.view.View.INVISIBLE; @@ -57,6 +53,11 @@ import static android.widget.Toast.LENGTH_LONG; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.ABORTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FAILED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FINISHED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.STARTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.WAITING; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -69,6 +70,8 @@ public class KeyAgreementFragment extends BaseEventFragment @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + @Inject + ViewModelProvider.Factory viewModelFactory; @Inject Provider keyAgreementTaskProvider; @Inject @@ -81,6 +84,7 @@ public class KeyAgreementFragment extends BaseEventFragment @Inject EventBus eventBus; + private ContactExchangeViewModel viewModel; private CameraView cameraView; private LinearLayout cameraOverlay; private View statusView; @@ -90,7 +94,6 @@ public class KeyAgreementFragment extends BaseEventFragment private boolean gotRemotePayload; private volatile boolean gotLocalPayload; private KeyAgreementTask task; - private KeyAgreementEventListener listener; public static KeyAgreementFragment newInstance() { Bundle args = new Bundle(); @@ -99,15 +102,11 @@ public class KeyAgreementFragment extends BaseEventFragment return fragment; } - @Override - public void onAttach(Context context) { - super.onAttach(context); - listener = (KeyAgreementEventListener) context; - } - @Override public void injectFragment(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(ContactExchangeViewModel.class); } @Override @@ -133,6 +132,10 @@ public class KeyAgreementFragment extends BaseEventFragment status = view.findViewById(R.id.connect_status); qrCodeView = view.findViewById(R.id.qr_code_view); qrCodeView.setFullscreenListener(this); + + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); + viewModel.getKeyAgreementState() + .observe(lifecycleOwner, this::onKeyAgreementStateChanged); } @Override @@ -271,48 +274,23 @@ public class KeyAgreementFragment extends BaseEventFragment KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e; gotLocalPayload = true; setQrCode(event.getLocalPayload()); - } else if (e instanceof KeyAgreementFailedEvent) { - keyAgreementFailed(); - } else if (e instanceof KeyAgreementWaitingEvent) { - keyAgreementWaiting(); - } else if (e instanceof KeyAgreementStartedEvent) { - keyAgreementStarted(); - } else if (e instanceof KeyAgreementAbortedEvent) { - KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e; - keyAgreementAborted(event.didRemoteAbort()); - } else if (e instanceof KeyAgreementFinishedEvent) { - keyAgreementFinished(((KeyAgreementFinishedEvent) e).getResult()); } } @UiThread - private void keyAgreementFailed() { - reset(); - listener.keyAgreementFailed(); - } - - @UiThread - private void keyAgreementWaiting() { - status.setText(listener.keyAgreementWaiting()); - } - - @UiThread - private void keyAgreementStarted() { - qrCodeView.setVisibility(INVISIBLE); - statusView.setVisibility(VISIBLE); - status.setText(listener.keyAgreementStarted()); - } - - @UiThread - private void keyAgreementAborted(boolean remoteAborted) { - reset(); - listener.keyAgreementAborted(remoteAborted); - } - - @UiThread - private void keyAgreementFinished(KeyAgreementResult result) { - statusView.setVisibility(VISIBLE); - status.setText(listener.keyAgreementFinished(result)); + private void onKeyAgreementStateChanged(KeyAgreementState state) { + if (state == WAITING) { + status.setText(R.string.waiting_for_contact_to_scan); + } else if (state == STARTED) { + qrCodeView.setVisibility(INVISIBLE); + statusView.setVisibility(VISIBLE); + status.setText(R.string.authenticating_with_device); + } else if (state == FINISHED) { + statusView.setVisibility(VISIBLE); + status.setText(R.string.exchanging_contact_details); + } else if (state == ABORTED || state == FAILED) { + reset(); + } } private void setQrCode(Payload localPayload) { @@ -328,7 +306,8 @@ public class KeyAgreementFragment extends BaseEventFragment // Use ISO 8859-1 to encode bytes directly as a string String content = new String(payloadBytes, ISO_8859_1); Bitmap qrCode = QrCodeUtils.createQrCode(dm, content); - runOnUiThreadUnlessDestroyed(() -> qrCodeView.setQrCode(qrCode)); + runOnUiThreadUnlessDestroyed( + () -> qrCodeView.setQrCode(qrCode)); }); } @@ -347,29 +326,4 @@ public class KeyAgreementFragment extends BaseEventFragment requireActivity().getSupportFragmentManager().popBackStack(); } - @NotNullByDefault - interface KeyAgreementEventListener { - - @UiThread - void keyAgreementFailed(); - - // Should return a string to be displayed as status. - @UiThread - @Nullable - String keyAgreementWaiting(); - - // Should return a string to be displayed as status. - @UiThread - @Nullable - String keyAgreementStarted(); - - // Will show an error fragment. - @UiThread - void keyAgreementAborted(boolean remoteAborted); - - // Should return a string to be displayed as status. - @UiThread - @Nullable - String keyAgreementFinished(KeyAgreementResult result); - } }