From 94ec22bef8164633babe42a3778eef399102b677 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 1 Feb 2021 11:14:07 -0300 Subject: [PATCH 01/21] Move keyagreement package into contact.add.nearby and fix some small warnings in the process --- briar-android/src/main/AndroidManifest.xml | 2 +- .../java/org/briarproject/briar/android/AppModule.java | 2 +- .../briar/android/activity/ActivityComponent.java | 8 ++++---- .../briar/android/contact/ContactListFragment.java | 2 +- .../add/nearby}/CameraException.java | 2 +- .../{keyagreement => contact/add/nearby}/CameraView.java | 4 ++-- .../add/nearby}/ContactExchangeActivity.java | 5 ++--- .../add/nearby}/ContactExchangeErrorFragment.java | 2 +- .../add/nearby}/ContactExchangeModule.java | 2 +- .../add/nearby}/ContactExchangeViewModel.java | 2 +- .../add/nearby}/IntroFragment.java | 2 +- .../add/nearby}/KeyAgreementActivity.java | 6 +++--- .../add/nearby}/KeyAgreementFragment.java | 4 ++-- .../add/nearby}/PreviewConsumer.java | 3 +-- .../add/nearby}/QrCodeDecoder.java | 2 +- .../{keyagreement => contact/add/nearby}/QrCodeUtils.java | 6 +++--- .../res/layout/activity_fragment_container_toolbar.xml | 4 ++-- .../src/main/res/layout/fragment_keyagreement_qr.xml | 2 +- 18 files changed, 29 insertions(+), 31 deletions(-) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/CameraException.java (76%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/CameraView.java (99%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/ContactExchangeActivity.java (95%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/ContactExchangeErrorFragment.java (97%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/ContactExchangeModule.java (87%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/ContactExchangeViewModel.java (98%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/IntroFragment.java (96%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/KeyAgreementActivity.java (98%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/KeyAgreementFragment.java (98%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/PreviewConsumer.java (75%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/QrCodeDecoder.java (98%) rename briar-android/src/main/java/org/briarproject/briar/android/{keyagreement => contact/add/nearby}/QrCodeUtils.java (89%) diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 988ea2b6f..693af657f 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -342,7 +342,7 @@ diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 0729aa0f2..08057a229 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -31,9 +31,9 @@ import org.briarproject.briar.android.account.DozeHelperModule; import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.SetupModule; import org.briarproject.briar.android.contact.ContactListModule; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeModule; import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.introduction.IntroductionModule; -import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.logging.LoggingModule; import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule; 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 3b1e91ef9..c8bf4b403 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 @@ -36,10 +36,10 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; 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.KeyAgreementActivity; -import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeErrorFragment; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementActivity; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.OpenDatabaseFragment; import org.briarproject.briar.android.login.PasswordFragment; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java index 9d32e0cab..48c3935ae 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java @@ -19,7 +19,7 @@ import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.fragment.BaseFragment; -import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.view.BriarRecyclerView; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraException.java similarity index 76% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraException.java index 0bff14ed7..04f7a0612 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraException.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import java.io.IOException; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java similarity index 99% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java index f21f4bfb8..1afa75ec3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.content.Context; import android.hardware.Camera; @@ -40,7 +40,6 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; -@SuppressWarnings("deprecation") @MethodsNotNullByDefault @ParametersNotNullByDefault public class CameraView extends SurfaceView implements SurfaceHolder.Callback, @@ -126,6 +125,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback, throw new CameraException(e); } setDisplayOrientation(getScreenRotationDegrees()); + if (camera == null) throw new CameraException("No camera found"); // Use barcode scene mode if it's available Parameters params = camera.getParameters(); params = setSceneMode(camera, params); 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/contact/add/nearby/ContactExchangeActivity.java similarity index 95% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java index 222fba77b..8b4b910c4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.os.Bundle; import android.widget.Toast; @@ -15,7 +15,6 @@ import javax.inject.Inject; import androidx.annotation.UiThread; import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelProviders; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; @@ -39,7 +38,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity { super.onCreate(state); requireNonNull(getSupportActionBar()) .setTitle(R.string.add_contact_title); - viewModel = ViewModelProviders.of(this, viewModelFactory) + viewModel = new ViewModelProvider(this, viewModelFactory) .get(ContactExchangeViewModel.class); } 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/contact/add/nearby/ContactExchangeErrorFragment.java similarity index 97% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeErrorFragment.java index be708d73c..6a73ed8a8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeErrorFragment.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.content.Intent; import android.os.Bundle; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeModule.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeModule.java similarity index 87% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeModule.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeModule.java index 05a539d5c..bdd7fd2ee 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeModule.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import org.briarproject.briar.android.viewmodel.ViewModelKey; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java index 054121cac..20fb5977e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.app.Application; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/IntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java similarity index 96% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/IntroFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java index 6ee1f45c2..f0ffe62c6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/IntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.content.Context; import android.os.Bundle; 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/contact/add/nearby/KeyAgreementActivity.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java index 19ea51cbf..506d84cd6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; @@ -24,8 +24,8 @@ import org.briarproject.briar.android.activity.ActivityComponent; 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.contact.add.nearby.IntroFragment.IntroScreenSeenListener; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment.KeyAgreementEventListener; import java.util.logging.Logger; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java index eb8f1bced..c8eb8e03b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.content.Context; import android.graphics.Bitmap; @@ -344,7 +344,7 @@ public class KeyAgreementFragment extends BaseEventFragment @Override protected void finish() { - getActivity().getSupportFragmentManager().popBackStack(); + requireActivity().getSupportFragmentManager().popBackStack(); } @NotNullByDefault diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/PreviewConsumer.java similarity index 75% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/PreviewConsumer.java index bea7b65b8..f94043255 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/PreviewConsumer.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.hardware.Camera; @@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import androidx.annotation.UiThread; -@SuppressWarnings("deprecation") @NotNullByDefault interface PreviewConsumer { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java index 91f6f9dda..f78f62769 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeUtils.java similarity index 89% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeUtils.java index 617f84b99..26f2bf050 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeUtils.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.keyagreement; +package org.briarproject.briar.android.contact.add.nearby; import android.graphics.Bitmap; import android.util.DisplayMetrics; @@ -18,13 +18,13 @@ import static android.graphics.Color.BLACK; import static android.graphics.Color.WHITE; import static com.google.zxing.BarcodeFormat.QR_CODE; import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault class QrCodeUtils { - private static final Logger LOG = - Logger.getLogger(QrCodeUtils.class.getName()); + private static final Logger LOG = getLogger(QrCodeUtils.class.getName()); @Nullable static Bitmap createQrCode(DisplayMetrics dm, String input) { diff --git a/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml b/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml index 8718e0b18..c9867a627 100644 --- a/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml +++ b/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml @@ -4,11 +4,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".android.keyagreement.KeyAgreementActivity"> + tools:context=".android.contact.add.nearby.KeyAgreementActivity"> - diff --git a/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml b/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml index 33227017d..6eb3d992e 100644 --- a/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml +++ b/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" android:keepScreenOn="true"> - From f6b3bde724976ee49b4f75535b6bbe9eb3101995 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 1 Feb 2021 14:01:46 -0300 Subject: [PATCH 02/21] Introduce ContactExchangeResult to include all result information in LiveData --- .../add/nearby/ContactExchangeActivity.java | 44 +++++++------------ .../add/nearby/ContactExchangeResult.java | 30 +++++++++++++ .../add/nearby/ContactExchangeViewModel.java | 38 +++++----------- .../add/nearby/KeyAgreementActivity.java | 13 +++++- 4 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java 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 8b4b910c4..8c031b375 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 @@ -8,13 +8,10 @@ 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.activity.ActivityComponent; import javax.annotation.Nullable; -import javax.inject.Inject; import androidx.annotation.UiThread; -import androidx.lifecycle.ViewModelProvider; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; @@ -23,40 +20,29 @@ import static java.util.Objects.requireNonNull; @ParametersNotNullByDefault public class ContactExchangeActivity extends KeyAgreementActivity { - @Inject - ViewModelProvider.Factory viewModelFactory; - - private ContactExchangeViewModel viewModel; - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); requireNonNull(getSupportActionBar()) .setTitle(R.string.add_contact_title); - viewModel = new ViewModelProvider(this, viewModelFactory) - .get(ContactExchangeViewModel.class); } - private void startContactExchange(KeyAgreementResult result) { - viewModel.getSucceeded().observe(this, succeeded -> { - if (succeeded == null) return; - if (succeeded) { - Author remote = requireNonNull(viewModel.getRemoteAuthor()); - contactExchangeSucceeded(remote); - } else { - Author duplicate = viewModel.getDuplicateAuthor(); - if (duplicate == null) contactExchangeFailed(); - else duplicateContact(duplicate); - } + 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(result.getTransportId(), - result.getConnection(), result.getMasterKey(), - result.wasAlice()); + viewModel.startContactExchange(agreementResult.getTransportId(), + agreementResult.getConnection(), agreementResult.getMasterKey(), + agreementResult.wasAlice()); } @UiThread diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java new file mode 100644 index 000000000..dad8a2a87 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java @@ -0,0 +1,30 @@ +package org.briarproject.briar.android.contact.add.nearby; + +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +abstract class ContactExchangeResult { + + static class Success extends ContactExchangeResult { + final Author remoteAuthor; + + Success(Author remoteAuthor) { + this.remoteAuthor = remoteAuthor; + } + } + + static class Error extends ContactExchangeResult { + @Nullable + final Author duplicateAuthor; + + Error(@Nullable Author duplicateAuthor) { + this.duplicateAuthor = duplicateAuthor; + } + } + +} 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 20fb5977e..1f4665548 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,17 +8,17 @@ 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.identity.Author; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success; import java.io.IOException; import java.util.concurrent.Executor; import java.util.logging.Logger; -import javax.annotation.Nullable; import javax.inject.Inject; import androidx.annotation.UiThread; @@ -39,10 +39,8 @@ class ContactExchangeViewModel extends AndroidViewModel { private final Executor ioExecutor; private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; - private final MutableLiveData succeeded = new MutableLiveData<>(); - - @Nullable - private volatile Author remoteAuthor, duplicateAuthor; + private final MutableLiveData exchangeResult = + new MutableLiveData<>(); @Inject ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor, @@ -62,18 +60,16 @@ class ContactExchangeViewModel extends AndroidViewModel { Contact contact = contactExchangeManager.exchangeContacts(conn, masterKey, alice, true); // Reuse the connection as a transport connection - connectionManager.manageOutgoingConnection(contact.getId(), - t, conn); - remoteAuthor = contact.getAuthor(); - succeeded.postValue(true); + connectionManager + .manageOutgoingConnection(contact.getId(), t, conn); + exchangeResult.postValue(new Success(contact.getAuthor())); } catch (ContactExistsException e) { tryToClose(conn); - duplicateAuthor = e.getRemoteAuthor(); - succeeded.postValue(false); + exchangeResult.postValue(new Error(e.getRemoteAuthor())); } catch (DbException | IOException e) { tryToClose(conn); logException(LOG, WARNING, e); - succeeded.postValue(false); + exchangeResult.postValue(new Error(null)); } }); } @@ -87,19 +83,7 @@ class ContactExchangeViewModel extends AndroidViewModel { } } - @UiThread - @Nullable - Author getRemoteAuthor() { - return remoteAuthor; - } - - @UiThread - @Nullable - Author getDuplicateAuthor() { - return duplicateAuthor; - } - - LiveData getSucceeded() { - return succeeded; + 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 506d84cd6..ca58bff16 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 @@ -22,10 +22,10 @@ import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; 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.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; import java.util.logging.Logger; @@ -38,6 +38,7 @@ import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProvider; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.CAMERA; @@ -97,12 +98,18 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private static final Logger LOG = getLogger(KeyAgreementActivity.class.getName()); + + @Inject + ViewModelProvider.Factory viewModelFactory; + @Inject EventBus eventBus; @Inject PluginManager pluginManager; + protected ContactExchangeViewModel viewModel; + /** * Set to true in onPostResume() and false in onPause(). This prevents the * QR code fragment from being shown if onRequestPermissionsResult() is @@ -140,6 +147,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements @Override public void injectActivity(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(this, viewModelFactory) + .get(ContactExchangeViewModel.class); } @Override From 6d1f1c785205201450802aefe3043a8ef34009ec Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 1 Feb 2021 15:22:50 -0300 Subject: [PATCH 03/21] 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); - } } From bed87ed43979ec32c4b4623abe15d6108f0277eb Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 2 Feb 2021 15:18:59 -0300 Subject: [PATCH 04/21] Move backend comms and logic out of KeyAgreementFragment into ViewModel --- .../keyagreement/KeyAgreementTransport.java | 4 +- .../add/nearby/ContactAddingState.java | 55 ++++ .../add/nearby/ContactExchangeActivity.java | 55 +++- .../add/nearby/ContactExchangeViewModel.java | 173 +++++++++++-- .../add/nearby/KeyAgreementActivity.java | 1 + .../add/nearby/KeyAgreementFragment.java | 241 ++++-------------- .../contact/add/nearby/QrCodeDecoder.java | 49 ++-- 7 files changed, 318 insertions(+), 260 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java index 5785575ee..f3c19076d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTransport.java @@ -34,12 +34,12 @@ class KeyAgreementTransport { Logger.getLogger(KeyAgreementTransport.class.getName()); // Accept records with current protocol version, known record type - private static Predicate ACCEPT = r -> + private static final Predicate ACCEPT = r -> r.getProtocolVersion() == PROTOCOL_VERSION && isKnownRecordType(r.getRecordType()); // Ignore records with current protocol version, unknown record type - private static Predicate IGNORE = r -> + private static final Predicate IGNORE = r -> r.getProtocolVersion() == PROTOCOL_VERSION && !isKnownRecordType(r.getRecordType()); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java new file mode 100644 index 000000000..51278b322 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java @@ -0,0 +1,55 @@ +package org.briarproject.briar.android.contact.add.nearby; + +import android.graphics.Bitmap; + +import androidx.annotation.Nullable; + +abstract class ContactAddingState { + + static class KeyAgreementListening extends ContactAddingState { + final Bitmap qrCode; + + KeyAgreementListening(Bitmap qrCode) { + this.qrCode = qrCode; + } + } + + static class QrCodeScanned extends ContactAddingState { + } + + static class KeyAgreementWaiting extends ContactAddingState { + } + + static class KeyAgreementStarted extends ContactAddingState { + } + + static class ContactExchangeStarted extends ContactAddingState { + } + + static class ContactExchangeFinished extends ContactAddingState { + final ContactExchangeResult result; + + ContactExchangeFinished(ContactExchangeResult result) { + this.result = result; + } + } + + static class Failed extends ContactAddingState { + /** + * 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; + } + + Failed() { + this(null); + } + } + +} 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 7d2a6e1e3..c4a435136 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 @@ -7,14 +7,15 @@ import org.briarproject.bramble.api.identity.Author; 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 org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.Failed; import javax.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + 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,15 +26,33 @@ 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); + viewModel.getState() + .observe(this, this::onContactAddingStateChanged); } - private void onKeyAgreementStateChanged(KeyAgreementState state) { - if (state == ABORTED || state == FAILED) { - showErrorFragment(); + @Override + public void onBackPressed() { + if (viewModel.getState().getValue() instanceof Failed) { + // finish this activity when going back in failed state + supportFinishAfterTransition(); + } else { + super.onBackPressed(); + } + } + + private void onContactAddingStateChanged(ContactAddingState state) { + if (state instanceof ContactExchangeFinished) { + ContactExchangeResult result = + ((ContactExchangeFinished) state).result; + onContactExchangeResult(result); + } else if (state instanceof Failed) { + // Remove navigation icon, so user can't go back when failed + // ErrorFragment will finish or relaunch this activity + Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setNavigationIcon(null); + + Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; + onAddingContactFailed(qrCodeTooOld); } } @@ -60,6 +79,22 @@ public class ContactExchangeActivity extends KeyAgreementActivity { } 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, + getString(R.string.app_name)); + } else { + msg = getString(R.string.qr_code_too_new, + getString(R.string.app_name)); + } + showNextFragment(ContactExchangeErrorFragment.newInstance(msg)); + } + } + private void showErrorFragment() { showNextFragment(new ContactExchangeErrorFragment()); } 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 19394e5fe..241e665ec 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 @@ -1,7 +1,13 @@ package org.briarproject.briar.android.contact.add.nearby; import android.app.Application; +import android.graphics.Bitmap; +import android.util.DisplayMetrics; +import android.widget.Toast; +import com.google.zxing.Result; + +import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactExchangeManager; @@ -12,95 +18,208 @@ 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.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.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.briar.R; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeStarted; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementListening; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementStarted; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting; import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success; import java.io.IOException; +import java.nio.charset.Charset; import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.inject.Inject; +import javax.inject.Provider; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.widget.Toast.LENGTH_LONG; +import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.INFO; 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 - implements EventListener { + implements EventListener, QrCodeDecoder.ResultCallback { private static final Logger LOG = getLogger(ContactExchangeViewModel.class.getName()); - enum KeyAgreementState { - WAITING, STARTED, FINISHED, ABORTED, FAILED - } + @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 + private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private final EventBus eventBus; private final Executor ioExecutor; + private final PayloadEncoder payloadEncoder; + private final PayloadParser payloadParser; + private final Provider keyAgreementTaskProvider; private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; - private final MutableLiveData keyAgreementState = - new MutableLiveData<>(); - private final MutableLiveData exchangeResult = + + private final MutableLiveData state = new MutableLiveData<>(); + final QrCodeDecoder qrCodeDecoder; + + @Nullable + private KeyAgreementTask task; + private volatile boolean gotLocalPayload = false, gotRemotePayload = false; + @Inject ContactExchangeViewModel(Application app, EventBus eventBus, @IoExecutor Executor ioExecutor, + PayloadEncoder payloadEncoder, + PayloadParser payloadParser, + Provider keyAgreementTaskProvider, ContactExchangeManager contactExchangeManager, ConnectionManager connectionManager) { super(app); this.eventBus = eventBus; this.ioExecutor = ioExecutor; + this.payloadEncoder = payloadEncoder; + this.payloadParser = payloadParser; + this.keyAgreementTaskProvider = keyAgreementTaskProvider; this.contactExchangeManager = contactExchangeManager; this.connectionManager = connectionManager; + qrCodeDecoder = new QrCodeDecoder(ioExecutor, this); + eventBus.addListener(this); } @Override protected void onCleared() { super.onCleared(); eventBus.removeListener(this); + stopListening(); + } + + /** + * Call this once Bluetooth and Wi-Fi are ready to be used. + * It is possible to call this more than once over the ViewModel's lifetime. + */ + @UiThread + void startListening() { + KeyAgreementTask oldTask = task; + KeyAgreementTask newTask = keyAgreementTaskProvider.get(); + task = newTask; + ioExecutor.execute(() -> { + if (oldTask != null) oldTask.stopListening(); + newTask.listen(); + }); + } + + @UiThread + private void stopListening() { + KeyAgreementTask oldTask = task; + ioExecutor.execute(() -> { + if (oldTask != null) oldTask.stopListening(); + }); } @Override public void eventOccurred(Event e) { - if (e instanceof KeyAgreementWaitingEvent) { - keyAgreementState.setValue(WAITING); + if (e instanceof KeyAgreementListeningEvent) { + LOG.info("KeyAgreementListeningEvent received"); + KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e; + onLocalPayloadReceived(event.getLocalPayload()); + } else if (e instanceof KeyAgreementWaitingEvent) { + LOG.info("KeyAgreementWaitingEvent received"); + state.setValue(new KeyAgreementWaiting()); } else if (e instanceof KeyAgreementStartedEvent) { - keyAgreementState.setValue(STARTED); - } else if (e instanceof KeyAgreementAbortedEvent) { - keyAgreementState.setValue(ABORTED); + LOG.info("KeyAgreementStartedEvent received"); + state.setValue(new KeyAgreementStarted()); } else if (e instanceof KeyAgreementFinishedEvent) { - keyAgreementState.setValue(FINISHED); + LOG.info("KeyAgreementFinishedEvent received"); KeyAgreementResult result = ((KeyAgreementFinishedEvent) e).getResult(); startContactExchange(result); + state.setValue(new ContactExchangeStarted()); + } else if (e instanceof KeyAgreementAbortedEvent) { + LOG.info("KeyAgreementAbortedEvent received"); + resetPayloadFlags(); + state.setValue(new ContactAddingState.Failed()); } else if (e instanceof KeyAgreementFailedEvent) { - keyAgreementState.setValue(FAILED); + LOG.info("KeyAgreementFailedEvent received"); + resetPayloadFlags(); + state.setValue(new ContactAddingState.Failed()); } } + /** + * This sets the QR code by setting the state to KeyAgreementListening. + */ + private void onLocalPayloadReceived(Payload localPayload) { + if (gotLocalPayload) return; + DisplayMetrics dm = getApplication().getResources().getDisplayMetrics(); + ioExecutor.execute(() -> { + byte[] payloadBytes = payloadEncoder.encode(localPayload); + if (LOG.isLoggable(INFO)) { + LOG.info("Local payload is " + payloadBytes.length + + " bytes"); + } + // 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); + gotLocalPayload = true; + state.postValue(new KeyAgreementListening(qrCode)); + }); + } + + @Override + @IoExecutor + public void onQrCodeDecoded(Result result) { + LOG.info("Got result from decoder"); + // Ignore results until the KeyAgreementTask is ready + if (!gotLocalPayload || gotRemotePayload) return; + try { + byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); + if (LOG.isLoggable(INFO)) + LOG.info("Remote payload is " + payloadBytes.length + " bytes"); + Payload remotePayload = payloadParser.parse(payloadBytes); + gotRemotePayload = true; + requireNonNull(task).connectAndRunProtocol(remotePayload); + state.postValue(new ContactAddingState.QrCodeScanned()); + } catch (UnsupportedVersionException e) { + resetPayloadFlags(); + state.postValue(new ContactAddingState.Failed(e.isTooOld())); + } catch (IOException | IllegalArgumentException e) { + LOG.log(WARNING, "QR Code Invalid", e); + Toast.makeText(getApplication(), R.string.qr_code_invalid, + LENGTH_LONG).show(); + resetPayloadFlags(); + state.postValue(new ContactAddingState.Failed()); + } + } + + private void resetPayloadFlags() { + gotRemotePayload = false; + gotLocalPayload = false; + } + @UiThread private void startContactExchange(KeyAgreementResult result) { TransportId t = result.getTransportId(); @@ -114,14 +233,17 @@ class ContactExchangeViewModel extends AndroidViewModel // Reuse the connection as a transport connection connectionManager .manageOutgoingConnection(contact.getId(), t, conn); - exchangeResult.postValue(new Success(contact.getAuthor())); + Success success = new Success(contact.getAuthor()); + state.postValue(new ContactExchangeFinished(success)); } catch (ContactExistsException e) { tryToClose(conn); - exchangeResult.postValue(new Error(e.getRemoteAuthor())); + Error error = new Error(e.getRemoteAuthor()); + state.postValue(new ContactExchangeFinished(error)); } catch (DbException | IOException e) { tryToClose(conn); logException(LOG, WARNING, e); - exchangeResult.postValue(new Error(null)); + Error error = new Error(null); + state.postValue(new ContactExchangeFinished(error)); } }); } @@ -135,11 +257,8 @@ class ContactExchangeViewModel extends AndroidViewModel } } - LiveData getKeyAgreementState() { - return keyAgreementState; + LiveData getState() { + return state; } - 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 6a35128e7..39f7d168b 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 @@ -205,6 +205,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements if (isResumed && continueClicked && areEssentialPermissionsGranted()) { if (isWifiReady() && isBluetoothReady()) { LOG.info("Wifi and Bluetooth are ready"); + viewModel.startListening(); showQrCodeFragment(); } else { if (shouldEnableWifi()) { 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 34a29281c..6d179af89 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 @@ -1,9 +1,7 @@ package org.briarproject.briar.android.contact.add.nearby; -import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; -import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,36 +10,24 @@ import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; import android.widget.Toast; -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.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.KeyAgreementListeningEvent; -import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; 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.contact.add.nearby.ContactAddingState.ContactExchangeStarted; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.Failed; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementStarted; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting; +import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.QrCodeScanned; +import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.view.QrCodeView; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.annotation.Nullable; 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; @@ -50,39 +36,20 @@ import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.widget.LinearLayout.HORIZONTAL; 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 -public class KeyAgreementFragment extends BaseEventFragment - implements QrCodeDecoder.ResultCallback, QrCodeView.FullscreenListener { +public class KeyAgreementFragment extends BaseFragment + implements QrCodeView.FullscreenListener { static final String TAG = KeyAgreementFragment.class.getName(); private static final Logger LOG = Logger.getLogger(TAG); - @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 - PayloadEncoder payloadEncoder; - @Inject - PayloadParser payloadParser; - @Inject - @IoExecutor - Executor ioExecutor; - @Inject - EventBus eventBus; private ContactExchangeViewModel viewModel; private CameraView cameraView; @@ -91,10 +58,6 @@ public class KeyAgreementFragment extends BaseEventFragment private QrCodeView qrCodeView; private TextView status; - private boolean gotRemotePayload; - private volatile boolean gotLocalPayload; - private KeyAgreementTask task; - public static KeyAgreementFragment newInstance() { Bundle args = new Bundle(); KeyAgreementFragment fragment = new KeyAgreementFragment(); @@ -109,11 +72,6 @@ public class KeyAgreementFragment extends BaseEventFragment .get(ContactExchangeViewModel.class); } - @Override - public String getUniqueTag() { - return TAG; - } - @Nullable @Override public View onCreateView(LayoutInflater inflater, @@ -133,16 +91,15 @@ public class KeyAgreementFragment extends BaseEventFragment qrCodeView = view.findViewById(R.id.qr_code_view); qrCodeView.setFullscreenListener(this); - LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); - viewModel.getKeyAgreementState() - .observe(lifecycleOwner, this::onKeyAgreementStateChanged); + viewModel.getState().observe(getViewLifecycleOwner(), + this::onContactAddingStateChanged); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); - cameraView.setPreviewConsumer(new QrCodeDecoder(this)); + cameraView.setPreviewConsumer(viewModel.qrCodeDecoder); } @Override @@ -153,7 +110,16 @@ public class KeyAgreementFragment extends BaseEventFragment } catch (CameraException e) { logCameraExceptionAndFinish(e); } - startListening(); + } + + @Override + public void onStop() { + super.onStop(); + try { + cameraView.stop(); + } catch (CameraException e) { + logCameraExceptionAndFinish(e); + } } @Override @@ -178,17 +144,42 @@ public class KeyAgreementFragment extends BaseEventFragment cameraOverlay.invalidate(); } - @Override - public void onStop() { - super.onStop(); - stopListening(); - try { - cameraView.stop(); - } catch (CameraException e) { - logCameraExceptionAndFinish(e); + @UiThread + private void onContactAddingStateChanged(ContactAddingState state) { + if (state instanceof ContactAddingState.KeyAgreementListening) { + Bitmap qrCode = + ((ContactAddingState.KeyAgreementListening) state).qrCode; + qrCodeView.setQrCode(qrCode); + } else if (state instanceof QrCodeScanned) { + try { + cameraView.stop(); + } catch (CameraException e) { + logCameraExceptionAndFinish(e); + } + cameraView.setVisibility(INVISIBLE); + statusView.setVisibility(VISIBLE); + status.setText(R.string.connecting_to_device); + } else if (state instanceof KeyAgreementWaiting) { + status.setText(R.string.waiting_for_contact_to_scan); + } else if (state instanceof KeyAgreementStarted) { + qrCodeView.setVisibility(INVISIBLE); + statusView.setVisibility(VISIBLE); + status.setText(R.string.authenticating_with_device); + } else if (state instanceof ContactExchangeStarted) { + statusView.setVisibility(VISIBLE); + status.setText(R.string.exchanging_contact_details); + } else if (state instanceof Failed) { + // the activity will replace this fragment with an error fragment + statusView.setVisibility(INVISIBLE); + cameraView.setVisibility(INVISIBLE); } } + @Override + public String getUniqueTag() { + return TAG; + } + @UiThread private void logCameraExceptionAndFinish(CameraException e) { logException(LOG, WARNING, e); @@ -197,130 +188,6 @@ public class KeyAgreementFragment extends BaseEventFragment finish(); } - @UiThread - private void startListening() { - KeyAgreementTask oldTask = task; - KeyAgreementTask newTask = keyAgreementTaskProvider.get(); - task = newTask; - ioExecutor.execute(() -> { - if (oldTask != null) oldTask.stopListening(); - newTask.listen(); - }); - } - - @UiThread - private void stopListening() { - KeyAgreementTask oldTask = task; - ioExecutor.execute(() -> { - if (oldTask != null) oldTask.stopListening(); - }); - } - - @UiThread - private void reset() { - // If we've stopped the camera view, restart it - if (gotRemotePayload) { - try { - cameraView.start(); - } catch (CameraException e) { - logCameraExceptionAndFinish(e); - return; - } - } - statusView.setVisibility(INVISIBLE); - cameraView.setVisibility(VISIBLE); - gotRemotePayload = false; - gotLocalPayload = false; - startListening(); - } - - @UiThread - private void qrCodeScanned(String content) { - try { - byte[] payloadBytes = content.getBytes(ISO_8859_1); - if (LOG.isLoggable(INFO)) - LOG.info("Remote payload is " + payloadBytes.length + " bytes"); - Payload remotePayload = payloadParser.parse(payloadBytes); - gotRemotePayload = true; - cameraView.stop(); - cameraView.setVisibility(INVISIBLE); - statusView.setVisibility(VISIBLE); - status.setText(R.string.connecting_to_device); - task.connectAndRunProtocol(remotePayload); - } catch (UnsupportedVersionException e) { - reset(); - String msg; - if (e.isTooOld()) { - msg = getString(R.string.qr_code_too_old, - getString(R.string.app_name)); - } else { - msg = getString(R.string.qr_code_too_new, - getString(R.string.app_name)); - } - showNextFragment(ContactExchangeErrorFragment.newInstance(msg)); - } catch (CameraException e) { - logCameraExceptionAndFinish(e); - } catch (IOException | IllegalArgumentException e) { - LOG.log(WARNING, "QR Code Invalid", e); - reset(); - Toast.makeText(getActivity(), R.string.qr_code_invalid, - LENGTH_LONG).show(); - } - } - - @Override - public void eventOccurred(Event e) { - if (e instanceof KeyAgreementListeningEvent) { - KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e; - gotLocalPayload = true; - setQrCode(event.getLocalPayload()); - } - } - - @UiThread - 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) { - Context context = getContext(); - if (context == null) return; - DisplayMetrics dm = context.getResources().getDisplayMetrics(); - ioExecutor.execute(() -> { - byte[] payloadBytes = payloadEncoder.encode(localPayload); - if (LOG.isLoggable(INFO)) { - LOG.info("Local payload is " + payloadBytes.length - + " bytes"); - } - // 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)); - }); - } - - @Override - public void handleResult(Result result) { - runOnUiThreadUnlessDestroyed(() -> { - LOG.info("Got result from decoder"); - // Ignore results until the KeyAgreementTask is ready - if (!gotLocalPayload) return; - if (!gotRemotePayload) qrCodeScanned(result.getText()); - }); - } - @Override protected void finish() { requireActivity().getSupportFragmentManager().popBackStack(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java index f78f62769..ad61baa3c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java @@ -4,7 +4,6 @@ import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.Size; -import android.os.AsyncTask; import com.google.zxing.BinaryBitmap; import com.google.zxing.LuminanceSource; @@ -15,10 +14,12 @@ import com.google.zxing.Result; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.qrcode.QRCodeReader; +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 java.util.concurrent.Executor; import java.util.logging.Logger; import androidx.annotation.UiThread; @@ -26,22 +27,23 @@ import androidx.annotation.UiThread; import static com.google.zxing.DecodeHintType.CHARACTER_SET; import static java.util.Collections.singletonMap; import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; -@SuppressWarnings("deprecation") @MethodsNotNullByDefault @ParametersNotNullByDefault class QrCodeDecoder implements PreviewConsumer, PreviewCallback { - private static final Logger LOG = - Logger.getLogger(QrCodeDecoder.class.getName()); + private static final Logger LOG = getLogger(QrCodeDecoder.class.getName()); + private final Executor ioExecutor; private final Reader reader = new QRCodeReader(); private final ResultCallback callback; private Camera camera = null; private int cameraIndex = 0; - QrCodeDecoder(ResultCallback callback) { + QrCodeDecoder(@IoExecutor Executor ioExecutor, ResultCallback callback) { + this.ioExecutor = ioExecutor; this.callback = callback; } @@ -74,8 +76,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { if (data.length == size.width * size.height * 3 / 2) { CameraInfo info = new CameraInfo(); Camera.getCameraInfo(cameraIndex, info); - new DecoderTask(data, size.width, size.height, - info.orientation).execute(); + decode(data, size.width, size.height, info.orientation); } else { // Camera parameters have changed - ask for a new preview LOG.info("Preview size does not match camera parameters"); @@ -89,43 +90,23 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { } } - private class DecoderTask extends AsyncTask { - - private final byte[] data; - private final int width, height, orientation; - - private DecoderTask(byte[] data, int width, int height, - int orientation) { - this.data = data; - this.width = width; - this.height = height; - this.orientation = orientation; - } - - @Override - protected Void doInBackground(Void... params) { + private void decode(byte[] data, int width, int height, int orientation) { + ioExecutor.execute(() -> { BinaryBitmap bitmap = binarize(data, width, height, orientation); Result result; try { result = reader.decode(bitmap, singletonMap(CHARACTER_SET, "ISO8859_1")); + callback.onQrCodeDecoded(result); } catch (ReaderException e) { // No barcode found - return null; } catch (RuntimeException e) { LOG.warning("Invalid preview frame"); - return null; } finally { reader.reset(); } - callback.handleResult(result); - return null; - } - - @Override - protected void onPostExecute(Void result) { - askForPreviewFrame(); - } + }); + askForPreviewFrame(); } private static BinaryBitmap binarize(byte[] data, int width, int height, @@ -143,7 +124,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { @NotNullByDefault interface ResultCallback { - - void handleResult(Result result); + @IoExecutor + void onQrCodeDecoded(Result result); } } From 5a55b3d7e3ade3acfcbb1c024a19f145e01ec615 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 2 Feb 2021 17:47:47 -0300 Subject: [PATCH 05/21] Move Plugin related code from activity to ViewModel --- .../android/activity/ActivityComponent.java | 11 +- .../add/nearby/ContactExchangeViewModel.java | 195 ++++++++++++- .../contact/add/nearby/IntroFragment.java | 36 +-- .../add/nearby/KeyAgreementActivity.java | 276 ++++-------------- 4 files changed, 280 insertions(+), 238 deletions(-) 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 c8bf4b403..18a9d26f8 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 @@ -21,6 +21,11 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.contact.ContactListFragment; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; +import org.briarproject.briar.android.contact.add.nearby.ContactExchangeErrorFragment; +import org.briarproject.briar.android.contact.add.nearby.IntroFragment; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementActivity; +import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment; import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment; @@ -36,10 +41,6 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; 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.contact.add.nearby.ContactExchangeActivity; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeErrorFragment; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementActivity; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.OpenDatabaseFragment; import org.briarproject.briar.android.login.PasswordFragment; @@ -208,6 +209,8 @@ public interface ActivityComponent { void inject(FeedFragment fragment); + void inject(IntroFragment fragment); + void inject(KeyAgreementFragment fragment); void inject(LinkExchangeFragment fragment); 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 241e665ec..0b0d11ec2 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 @@ -1,6 +1,7 @@ package org.briarproject.briar.android.contact.add.nearby; import android.app.Application; +import android.bluetooth.BluetoothAdapter; import android.graphics.Bitmap; import android.util.DisplayMetrics; import android.widget.Toast; @@ -30,8 +31,14 @@ 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.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.Plugin.State; +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.briar.R; import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeStarted; @@ -40,6 +47,8 @@ import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyA import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting; import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success; +import org.briarproject.briar.android.viewmodel.LiveEvent; +import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import java.io.IOException; import java.nio.charset.Charset; @@ -55,12 +64,19 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; +import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; +import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; @NotNullByDefault class ContactExchangeViewModel extends AndroidViewModel @@ -69,22 +85,80 @@ class ContactExchangeViewModel extends AndroidViewModel private static final Logger LOG = getLogger(ContactExchangeViewModel.class.getName()); + enum BluetoothDecision { + /** + * We haven't asked the user about Bluetooth discoverability. + */ + UNKNOWN, + + /** + * The device doesn't have a Bluetooth adapter. + */ + NO_ADAPTER, + + /** + * We're waiting for the user to accept or refuse discoverability. + */ + WAITING, + + /** + * The user has accepted discoverability. + */ + ACCEPTED, + + /** + * The user has refused discoverability. + */ + REFUSED + } + @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private final EventBus eventBus; private final Executor ioExecutor; + private final PluginManager pluginManager; private final PayloadEncoder payloadEncoder; private final PayloadParser payloadParser; private final Provider keyAgreementTaskProvider; private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; + /** + * Set to true when the continue button is clicked, and false when the QR + * code fragment is shown. This prevents the QR code fragment from being + * shown automatically before the continue button has been clicked. + */ + private final MutableLiveData wasContinueClicked = + new MutableLiveData<>(false); + private final MutableLiveEvent showQrCodeFragment = + new MutableLiveEvent<>(); + private final MutableLiveEvent transportStateChanged = + new MutableLiveEvent<>(); private final MutableLiveData state = new MutableLiveData<>(); final QrCodeDecoder qrCodeDecoder; + @Nullable + private final BluetoothAdapter bt; + @Nullable + private final Plugin wifiPlugin, bluetoothPlugin; + // UiThread + BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; + + /** + * Records whether we've enabled the wifi plugin so we don't enable it more + * than once. + */ + private boolean hasEnabledWifi = false; + + /** + * Records whether we've enabled the Bluetooth plugin so we don't enable it + * more than once. + */ + private boolean hasEnabledBluetooth = false; + @Nullable private KeyAgreementTask task; private volatile boolean gotLocalPayload = false, gotRemotePayload = false; @@ -93,6 +167,7 @@ class ContactExchangeViewModel extends AndroidViewModel ContactExchangeViewModel(Application app, EventBus eventBus, @IoExecutor Executor ioExecutor, + PluginManager pluginManager, PayloadEncoder payloadEncoder, PayloadParser payloadParser, Provider keyAgreementTaskProvider, @@ -101,11 +176,15 @@ class ContactExchangeViewModel extends AndroidViewModel super(app); this.eventBus = eventBus; this.ioExecutor = ioExecutor; + this.pluginManager = pluginManager; this.payloadEncoder = payloadEncoder; this.payloadParser = payloadParser; this.keyAgreementTaskProvider = keyAgreementTaskProvider; this.contactExchangeManager = contactExchangeManager; this.connectionManager = connectionManager; + bt = BluetoothAdapter.getDefaultAdapter(); + wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID); + bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); qrCodeDecoder = new QrCodeDecoder(ioExecutor, this); eventBus.addListener(this); } @@ -117,12 +196,95 @@ class ContactExchangeViewModel extends AndroidViewModel stopListening(); } + @UiThread + void onContinueClicked() { + if (bluetoothDecision == REFUSED) { + bluetoothDecision = UNKNOWN; // Ask again + } + wasContinueClicked.setValue(true); + } + + @UiThread + boolean isBluetoothSupported() { + return bt != null && bluetoothPlugin != null; + } + + @UiThread + boolean isWifiReady() { + if (wifiPlugin == null) return true; // Continue without wifi + State state = wifiPlugin.getState(); + // Wait for plugin to become enabled + return state == ACTIVE || state == INACTIVE; + } + + @UiThread + boolean isBluetoothReady() { + if (bt == null || bluetoothPlugin == null) { + // Continue without Bluetooth + return true; + } + if (bluetoothDecision == BluetoothDecision.UNKNOWN || + bluetoothDecision == BluetoothDecision.WAITING || + bluetoothDecision == BluetoothDecision.REFUSED) { + // Wait for user to accept + return false; + } + if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + // Wait for adapter to become discoverable + return false; + } + // Wait for plugin to become active + return bluetoothPlugin.getState() == ACTIVE; + } + + @UiThread + void enableWifiIfWeShould() { + if (hasEnabledWifi) return; + if (wifiPlugin == null) return; + State state = wifiPlugin.getState(); + if (state == STARTING_STOPPING || state == DISABLED) { + LOG.info("Enabling wifi plugin"); + hasEnabledWifi = true; + pluginManager.setPluginEnabled(LanTcpConstants.ID, true); + } + } + + @UiThread + void enableBluetoothIfWeShould() { + if (bluetoothDecision != BluetoothDecision.ACCEPTED) return; + if (hasEnabledBluetooth) return; + if (bluetoothPlugin == null || !isBluetoothSupported()) return; + State state = bluetoothPlugin.getState(); + if (state == STARTING_STOPPING || state == DISABLED) { + LOG.info("Enabling Bluetooth plugin"); + hasEnabledBluetooth = true; + pluginManager.setPluginEnabled(BluetoothConstants.ID, true); + } + } + + @UiThread + void startAddingContact() { + // If we return to the intro fragment, the continue button needs to be + // clicked again before showing the QR code fragment + wasContinueClicked.setValue(false); + // If we return to the intro fragment, ask for Bluetooth + // discoverability again before showing the QR code fragment + bluetoothDecision = UNKNOWN; + // If we return to the intro fragment, we may need to enable wifi and + // Bluetooth again + hasEnabledWifi = false; + hasEnabledBluetooth = false; + // start to listen with a KeyAgreementTask + startListening(); + showQrCodeFragment.setEvent(true); + } + /** * Call this once Bluetooth and Wi-Fi are ready to be used. * It is possible to call this more than once over the ViewModel's lifetime. */ @UiThread - void startListening() { + private void startListening() { KeyAgreementTask oldTask = task; KeyAgreementTask newTask = keyAgreementTaskProvider.get(); task = newTask; @@ -142,7 +304,20 @@ class ContactExchangeViewModel extends AndroidViewModel @Override public void eventOccurred(Event e) { - if (e instanceof KeyAgreementListeningEvent) { + if (e instanceof TransportStateEvent) { + TransportStateEvent t = (TransportStateEvent) e; + if (t.getTransportId().equals(BluetoothConstants.ID)) { + if (LOG.isLoggable(INFO)) { + LOG.info("Bluetooth state changed to " + t.getState()); + } + transportStateChanged.setEvent(t.getTransportId()); + } else if (t.getTransportId().equals(LanTcpConstants.ID)) { + if (LOG.isLoggable(INFO)) { + LOG.info("Wifi state changed to " + t.getState()); + } + transportStateChanged.setEvent(t.getTransportId()); + } + } else if (e instanceof KeyAgreementListeningEvent) { LOG.info("KeyAgreementListeningEvent received"); KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e; onLocalPayloadReceived(event.getLocalPayload()); @@ -257,6 +432,22 @@ class ContactExchangeViewModel extends AndroidViewModel } } + LiveData getWasContinueClicked() { + return wasContinueClicked; + } + + /** + * Receives an event when the transport state of the WiFi or Bluetooth + * plugins changes. + */ + LiveEvent getTransportStateChanged() { + return transportStateChanged; + } + + LiveEvent getShowQrCodeFragment() { + return showQrCodeFragment; + } + LiveData getState() { return state; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java index f0ffe62c6..4b7a6201f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.contact.add.nearby; -import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -10,9 +9,13 @@ import android.widget.ScrollView; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.fragment.BaseFragment; import javax.annotation.Nullable; +import javax.inject.Inject; + +import androidx.lifecycle.ViewModelProvider; import static android.view.View.FOCUS_DOWN; @@ -20,33 +23,27 @@ import static android.view.View.FOCUS_DOWN; @ParametersNotNullByDefault public class IntroFragment extends BaseFragment { - interface IntroScreenSeenListener { - void showNextScreen(); - } - public static final String TAG = IntroFragment.class.getName(); - private IntroScreenSeenListener screenSeenListener; + @Inject + ViewModelProvider.Factory viewModelFactory; + + private ContactExchangeViewModel viewModel; + private ScrollView scrollView; public static IntroFragment newInstance() { - Bundle args = new Bundle(); - IntroFragment fragment = new IntroFragment(); fragment.setArguments(args); return fragment; } @Override - public void onAttach(Context context) { - super.onAttach(context); - screenSeenListener = (IntroScreenSeenListener) context; - } - - @Override - public String getUniqueTag() { - return TAG; + public void injectFragment(ActivityComponent component) { + component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(ContactExchangeViewModel.class); } @Nullable @@ -59,7 +56,7 @@ public class IntroFragment extends BaseFragment { false); scrollView = v.findViewById(R.id.scrollView); View button = v.findViewById(R.id.continueButton); - button.setOnClickListener(view -> screenSeenListener.showNextScreen()); + button.setOnClickListener(view -> viewModel.onContinueClicked()); return v; } @@ -69,4 +66,9 @@ public class IntroFragment extends BaseFragment { scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); } + @Override + public String getUniqueTag() { + return TAG; + } + } 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 39f7d168b..6eb87f38b 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 @@ -1,6 +1,5 @@ package org.briarproject.briar.android.contact.add.nearby; -import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -8,21 +7,12 @@ import android.content.IntentFilter; import android.os.Bundle; import android.view.MenuItem; -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.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.bramble.api.plugin.BluetoothConstants; -import org.briarproject.bramble.api.plugin.LanTcpConstants; -import org.briarproject.bramble.api.plugin.Plugin; -import org.briarproject.bramble.api.plugin.Plugin.State; -import org.briarproject.bramble.api.plugin.PluginManager; -import org.briarproject.bramble.api.plugin.event.TransportStateEvent; 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.ContactExchangeViewModel.BluetoothDecision; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; @@ -43,51 +33,21 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.CAMERA; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; -import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; -import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; -import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; -import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; -import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; -import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.ACCEPTED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; +import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; @MethodsNotNullByDefault @ParametersNotNullByDefault -public abstract class KeyAgreementActivity extends BriarActivity implements - BaseFragmentListener, IntroScreenSeenListener, EventListener { - - private enum BluetoothDecision { - /** - * We haven't asked the user about Bluetooth discoverability. - */ - UNKNOWN, - - /** - * The device doesn't have a Bluetooth adapter. - */ - NO_ADAPTER, - - /** - * We're waiting for the user to accept or refuse discoverability. - */ - WAITING, - - /** - * The user has accepted discoverability. - */ - ACCEPTED, - - /** - * The user has refused discoverability. - */ - REFUSED - } +public abstract class KeyAgreementActivity extends BriarActivity + implements BaseFragmentListener { private enum Permission { UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED @@ -96,16 +56,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private static final Logger LOG = getLogger(KeyAgreementActivity.class.getName()); - @Inject ViewModelProvider.Factory viewModelFactory; - @Inject - EventBus eventBus; - - @Inject - PluginManager pluginManager; - protected ContactExchangeViewModel viewModel; /** @@ -116,31 +69,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements */ private boolean isResumed = false; - /** - * Set to true when the continue button is clicked, and false when the QR - * code fragment is shown. This prevents the QR code fragment from being - * shown automatically before the continue button has been clicked. - */ - private boolean continueClicked = false; - - /** - * Records whether we've enabled the wifi plugin so we don't enable it more - * than once. - */ - private boolean hasEnabledWifi = false; - - /** - * Records whether we've enabled the Bluetooth plugin so we don't enable it - * more than once. - */ - private boolean hasEnabledBluetooth = false; - private Permission cameraPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN; - private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; private BroadcastReceiver bluetoothReceiver = null; - private Plugin wifiPlugin = null, bluetoothPlugin = null; - private BluetoothAdapter bt = null; @Override public void injectActivity(ActivityComponent component) { @@ -162,9 +93,42 @@ public abstract class KeyAgreementActivity extends BriarActivity implements IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); bluetoothReceiver = new BluetoothStateReceiver(); registerReceiver(bluetoothReceiver, filter); - wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID); - bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); - bt = BluetoothAdapter.getDefaultAdapter(); + viewModel.getWasContinueClicked().observe(this, clicked -> { + if (clicked && checkPermissions()) showQrCodeFragmentIfAllowed(); + }); + viewModel.getTransportStateChanged().observeEvent(this, + t -> showQrCodeFragmentIfAllowed()); + viewModel.getShowQrCodeFragment().observeEvent(this, show -> { + if (show) showQrCodeFragment(); + }); + } + + @Override + public void onStart() { + super.onStart(); + // Permissions may have been granted manually while we were stopped + cameraPermission = Permission.UNKNOWN; + locationPermission = Permission.UNKNOWN; + } + + @Override + protected void onPostResume() { + super.onPostResume(); + isResumed = true; + // Workaround for + // https://code.google.com/p/android/issues/detail?id=190966 + showQrCodeFragmentIfAllowed(); + } + + @Override + protected void onPause() { + super.onPause(); + isResumed = false; + } + + @Override + protected void onStop() { + super.onStop(); } @Override @@ -182,45 +146,22 @@ public abstract class KeyAgreementActivity extends BriarActivity implements return super.onOptionsItemSelected(item); } - @Override - public void onStart() { - super.onStart(); - eventBus.addListener(this); - // Permissions may have been granted manually while we were stopped - cameraPermission = Permission.UNKNOWN; - locationPermission = Permission.UNKNOWN; - } - - @Override - protected void onPostResume() { - super.onPostResume(); - isResumed = true; - // Workaround for - // https://code.google.com/p/android/issues/detail?id=190966 - showQrCodeFragmentIfAllowed(); - } - @SuppressWarnings("StatementWithEmptyBody") private void showQrCodeFragmentIfAllowed() { + boolean continueClicked = // never set to null + requireNonNull(viewModel.getWasContinueClicked().getValue()); if (isResumed && continueClicked && areEssentialPermissionsGranted()) { - if (isWifiReady() && isBluetoothReady()) { + if (viewModel.isWifiReady() && viewModel.isBluetoothReady()) { LOG.info("Wifi and Bluetooth are ready"); - viewModel.startListening(); - showQrCodeFragment(); + viewModel.startAddingContact(); } else { - if (shouldEnableWifi()) { - LOG.info("Enabling wifi plugin"); - hasEnabledWifi = true; - pluginManager.setPluginEnabled(LanTcpConstants.ID, true); - } - if (bluetoothDecision == BluetoothDecision.UNKNOWN) { + viewModel.enableWifiIfWeShould(); + if (viewModel.bluetoothDecision == UNKNOWN) { requestBluetoothDiscoverable(); - } else if (bluetoothDecision == BluetoothDecision.REFUSED) { + } else if (viewModel.bluetoothDecision == REFUSED) { // Ask again when the user clicks "continue" - } else if (shouldEnableBluetooth()) { - LOG.info("Enabling Bluetooth plugin"); - hasEnabledBluetooth = true; - pluginManager.setPluginEnabled(BluetoothConstants.ID, true); + } else { + viewModel.enableBluetoothIfWeShould(); } } } @@ -229,119 +170,42 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private boolean areEssentialPermissionsGranted() { return cameraPermission == Permission.GRANTED && (SDK_INT < 23 || locationPermission == Permission.GRANTED || - !isBluetoothSupported()); - } - - private boolean isBluetoothSupported() { - return bt != null && bluetoothPlugin != null; - } - - private boolean isWifiReady() { - if (wifiPlugin == null) return true; // Continue without wifi - State state = wifiPlugin.getState(); - // Wait for plugin to become enabled - return state == ACTIVE || state == INACTIVE; - } - - private boolean isBluetoothReady() { - if (!isBluetoothSupported()) { - // Continue without Bluetooth - return true; - } - if (bluetoothDecision == BluetoothDecision.UNKNOWN || - bluetoothDecision == BluetoothDecision.WAITING || - bluetoothDecision == BluetoothDecision.REFUSED) { - // Wait for user to accept - return false; - } - if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - // Wait for adapter to become discoverable - return false; - } - // Wait for plugin to become active - return bluetoothPlugin.getState() == ACTIVE; - } - - private boolean shouldEnableWifi() { - if (hasEnabledWifi) return false; - if (wifiPlugin == null) return false; - State state = wifiPlugin.getState(); - return state == STARTING_STOPPING || state == DISABLED; + !viewModel.isBluetoothSupported()); } private void requestBluetoothDiscoverable() { - if (!isBluetoothSupported()) { - bluetoothDecision = BluetoothDecision.NO_ADAPTER; + if (!viewModel.isBluetoothSupported()) { + viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; showQrCodeFragmentIfAllowed(); } else { Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); if (i.resolveActivity(getPackageManager()) != null) { LOG.info("Asking for Bluetooth discoverability"); - bluetoothDecision = BluetoothDecision.WAITING; + viewModel.bluetoothDecision = BluetoothDecision.WAITING; startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE); } else { - bluetoothDecision = BluetoothDecision.NO_ADAPTER; + viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; showQrCodeFragmentIfAllowed(); } } } - private boolean shouldEnableBluetooth() { - if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false; - if (hasEnabledBluetooth) return false; - if (!isBluetoothSupported()) return false; - State state = bluetoothPlugin.getState(); - return state == STARTING_STOPPING || state == DISABLED; - } - - @Override - protected void onPause() { - super.onPause(); - isResumed = false; - } - - @Override - protected void onStop() { - super.onStop(); - eventBus.removeListener(this); - } - - @Override - public void showNextScreen() { - continueClicked = true; - if (bluetoothDecision == BluetoothDecision.REFUSED) { - bluetoothDecision = BluetoothDecision.UNKNOWN; // Ask again - } - if (checkPermissions()) showQrCodeFragmentIfAllowed(); - } - @Override public void onActivityResult(int request, int result, @Nullable Intent data) { if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { if (result == RESULT_CANCELED) { LOG.info("Bluetooth discoverability was refused"); - bluetoothDecision = BluetoothDecision.REFUSED; + viewModel.bluetoothDecision = REFUSED; } else { LOG.info("Bluetooth discoverability was accepted"); - bluetoothDecision = BluetoothDecision.ACCEPTED; + viewModel.bluetoothDecision = ACCEPTED; } showQrCodeFragmentIfAllowed(); } else super.onActivityResult(request, result, data); } private void showQrCodeFragment() { - // If we return to the intro fragment, the continue button needs to be - // clicked again before showing the QR code fragment - continueClicked = false; - // If we return to the intro fragment, ask for Bluetooth - // discoverability again before showing the QR code fragment - bluetoothDecision = BluetoothDecision.UNKNOWN; - // If we return to the intro fragment, we may need to enable wifi and - // Bluetooth again - hasEnabledWifi = false; - hasEnabledBluetooth = false; - // FIXME #824 FragmentManager fm = getSupportFragmentManager(); if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { @@ -362,7 +226,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements R.string.permission_camera_denied_body); return false; } - if (isBluetoothSupported() && + if (viewModel.isBluetoothSupported() && locationPermission == Permission.PERMANENTLY_DENIED) { showDenialDialog(R.string.permission_location_title, R.string.permission_location_denied_body); @@ -406,7 +270,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements private void requestPermissions() { String[] permissions; - if (isBluetoothSupported()) { + if (viewModel.isBluetoothSupported()) { permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION}; } else { permissions = new String[] {CAMERA}; @@ -430,7 +294,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements } else { cameraPermission = Permission.PERMANENTLY_DENIED; } - if (isBluetoothSupported()) { + if (viewModel.isBluetoothSupported()) { if (gotPermission(ACCESS_FINE_LOCATION, permissions, grantResults)) { locationPermission = Permission.GRANTED; @@ -463,24 +327,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements permission); } - @Override - public void eventOccurred(Event e) { - if (e instanceof TransportStateEvent) { - TransportStateEvent t = (TransportStateEvent) e; - if (t.getTransportId().equals(BluetoothConstants.ID)) { - if (LOG.isLoggable(INFO)) { - LOG.info("Bluetooth state changed to " + t.getState()); - } - showQrCodeFragmentIfAllowed(); - } else if (t.getTransportId().equals(LanTcpConstants.ID)) { - if (LOG.isLoggable(INFO)) { - LOG.info("Wifi state changed to " + t.getState()); - } - showQrCodeFragmentIfAllowed(); - } - } - } - private class BluetoothStateReceiver extends BroadcastReceiver { @Override From d8327d6de2b1aff78ba9a73fd472caf0432d8161 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 2 Feb 2021 17:48:08 -0300 Subject: [PATCH 06/21] Re-set orientation lock when fragment is left --- .../android/contact/add/nearby/KeyAgreementFragment.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 6d179af89..2aa53fd50 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 @@ -31,6 +31,7 @@ import androidx.annotation.UiThread; import androidx.lifecycle.ViewModelProvider; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; @@ -122,6 +123,13 @@ public class KeyAgreementFragment extends BaseFragment } } + @Override + public void onDestroy() { + requireActivity() + .setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + super.onDestroy(); + } + @Override public void setFullscreen(boolean fullscreen) { LinearLayout.LayoutParams statusParams, qrCodeParams; From 700f6e05bf2046cb790a04b751aa73e1212922d7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 4 Feb 2021 14:07:53 -0300 Subject: [PATCH 07/21] Factor out permission related code from KeyAgreementActivity to AddNearbyContactPermissionManager --- .../AddNearbyContactPermissionManager.java | 154 +++++++++++++ .../add/nearby/KeyAgreementActivity.java | 212 ++++-------------- 2 files changed, 199 insertions(+), 167 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java new file mode 100644 index 000000000..1244da3ae --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -0,0 +1,154 @@ +package org.briarproject.briar.android.contact.add.nearby; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.BaseActivity; + +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.CAMERA; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION.SDK_INT; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; +import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; + +class AddNearbyContactPermissionManager { + + private enum Permission { + UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED + } + + private Permission cameraPermission = Permission.UNKNOWN; + private Permission locationPermission = Permission.UNKNOWN; + + private final BaseActivity ctx; + private final boolean isBluetoothSupported; + + AddNearbyContactPermissionManager(BaseActivity ctx, + boolean isBluetoothSupported) { + this.ctx = ctx; + this.isBluetoothSupported = isBluetoothSupported; + } + + void resetPermissions() { + cameraPermission = Permission.UNKNOWN; + locationPermission = Permission.UNKNOWN; + } + + boolean areEssentialPermissionsGranted() { + return cameraPermission == Permission.GRANTED && + (SDK_INT < 23 || locationPermission == Permission.GRANTED || + !isBluetoothSupported); + } + + boolean checkPermissions() { + if (areEssentialPermissionsGranted()) return true; + // If an essential permission has been permanently denied, ask the + // user to change the setting + if (cameraPermission == Permission.PERMANENTLY_DENIED) { + showDenialDialog(R.string.permission_camera_title, + R.string.permission_camera_denied_body); + return false; + } + if (isBluetoothSupported && + locationPermission == Permission.PERMANENTLY_DENIED) { + showDenialDialog(R.string.permission_location_title, + R.string.permission_location_denied_body); + return false; + } + // Should we show the rationale for one or both permissions? + if (cameraPermission == Permission.SHOW_RATIONALE && + locationPermission == Permission.SHOW_RATIONALE) { + showRationale(R.string.permission_camera_location_title, + R.string.permission_camera_location_request_body); + } else if (cameraPermission == Permission.SHOW_RATIONALE) { + showRationale(R.string.permission_camera_title, + R.string.permission_camera_request_body); + } else if (locationPermission == Permission.SHOW_RATIONALE) { + showRationale(R.string.permission_location_title, + R.string.permission_location_request_body); + } else { + requestPermissions(); + } + return false; + } + + private void showDenialDialog(@StringRes int title, @StringRes int body) { + AlertDialog.Builder builder = + new AlertDialog.Builder(ctx, R.style.BriarDialogTheme); + builder.setTitle(title); + builder.setMessage(body); + builder.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx)); + builder.setNegativeButton(R.string.cancel, + (dialog, which) -> ctx.supportFinishAfterTransition()); + builder.show(); + } + + private void showRationale(@StringRes int title, @StringRes int body) { + AlertDialog.Builder builder = + new AlertDialog.Builder(ctx, R.style.BriarDialogTheme); + builder.setTitle(title); + builder.setMessage(body); + builder.setNeutralButton(R.string.continue_button, + (dialog, which) -> requestPermissions()); + builder.show(); + } + + private void requestPermissions() { + String[] permissions; + if (isBluetoothSupported) { + permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION}; + } else { + permissions = new String[] {CAMERA}; + } + ActivityCompat.requestPermissions(ctx, permissions, + REQUEST_PERMISSION_CAMERA_LOCATION); + } + + void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults, Runnable onPermissionsGranted) { + if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION) + throw new AssertionError(); + if (gotPermission(CAMERA, permissions, grantResults)) { + cameraPermission = Permission.GRANTED; + } else if (shouldShowRationale(CAMERA)) { + cameraPermission = Permission.SHOW_RATIONALE; + } else { + cameraPermission = Permission.PERMANENTLY_DENIED; + } + if (isBluetoothSupported) { + if (gotPermission(ACCESS_FINE_LOCATION, permissions, + grantResults)) { + locationPermission = Permission.GRANTED; + } else if (shouldShowRationale(ACCESS_FINE_LOCATION)) { + locationPermission = Permission.SHOW_RATIONALE; + } else { + locationPermission = Permission.PERMANENTLY_DENIED; + } + } + // If a permission dialog has been shown, showing the QR code fragment + // on this call path would cause a crash due to + // https://code.google.com/p/android/issues/detail?id=190966. + // In that case the isResumed flag prevents the fragment from being + // shown here, and showQrCodeFragmentIfAllowed() will be called again + // from onPostResume(). + if (checkPermissions()) onPermissionsGranted.run(); + } + + private boolean gotPermission(String permission, String[] permissions, + int[] grantResults) { + for (int i = 0; i < permissions.length; i++) { + if (permission.equals(permissions[i])) + return grantResults[i] == PERMISSION_GRANTED; + } + return false; + } + + private boolean shouldShowRationale(String permission) { + return ActivityCompat + .shouldShowRequestPermissionRationale(ctx, permission); + } + +} 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 6eb87f38b..6c8b76a41 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 @@ -21,38 +21,25 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; -import androidx.annotation.StringRes; import androidx.annotation.UiThread; -import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.widget.Toolbar; -import androidx.core.app.ActivityCompat; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; -import static android.Manifest.permission.ACCESS_FINE_LOCATION; -import static android.Manifest.permission.CAMERA; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Build.VERSION.SDK_INT; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; -import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.ACCEPTED; import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; -import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; @MethodsNotNullByDefault @ParametersNotNullByDefault public abstract class KeyAgreementActivity extends BriarActivity implements BaseFragmentListener { - private enum Permission { - UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED - } - private static final Logger LOG = getLogger(KeyAgreementActivity.class.getName()); @@ -60,6 +47,7 @@ public abstract class KeyAgreementActivity extends BriarActivity ViewModelProvider.Factory viewModelFactory; protected ContactExchangeViewModel viewModel; + private AddNearbyContactPermissionManager permissionManager; /** * Set to true in onPostResume() and false in onPause(). This prevents the @@ -68,9 +56,6 @@ public abstract class KeyAgreementActivity extends BriarActivity * https://issuetracker.google.com/issues/37067655. */ private boolean isResumed = false; - - private Permission cameraPermission = Permission.UNKNOWN; - private Permission locationPermission = Permission.UNKNOWN; private BroadcastReceiver bluetoothReceiver = null; @Override @@ -78,6 +63,8 @@ public abstract class KeyAgreementActivity extends BriarActivity component.inject(this); viewModel = new ViewModelProvider(this, viewModelFactory) .get(ContactExchangeViewModel.class); + permissionManager = new AddNearbyContactPermissionManager(this, + viewModel.isBluetoothSupported()); } @Override @@ -94,7 +81,9 @@ public abstract class KeyAgreementActivity extends BriarActivity bluetoothReceiver = new BluetoothStateReceiver(); registerReceiver(bluetoothReceiver, filter); viewModel.getWasContinueClicked().observe(this, clicked -> { - if (clicked && checkPermissions()) showQrCodeFragmentIfAllowed(); + if (clicked && permissionManager.checkPermissions()) { + showQrCodeFragmentIfAllowed(); + } }); viewModel.getTransportStateChanged().observeEvent(this, t -> showQrCodeFragmentIfAllowed()); @@ -107,8 +96,7 @@ public abstract class KeyAgreementActivity extends BriarActivity public void onStart() { super.onStart(); // Permissions may have been granted manually while we were stopped - cameraPermission = Permission.UNKNOWN; - locationPermission = Permission.UNKNOWN; + permissionManager.resetPermissions(); } @Override @@ -126,11 +114,6 @@ public abstract class KeyAgreementActivity extends BriarActivity isResumed = false; } - @Override - protected void onStop() { - super.onStop(); - } - @Override public void onDestroy() { super.onDestroy(); @@ -146,31 +129,29 @@ public abstract class KeyAgreementActivity extends BriarActivity return super.onOptionsItemSelected(item); } - @SuppressWarnings("StatementWithEmptyBody") - private void showQrCodeFragmentIfAllowed() { - boolean continueClicked = // never set to null - requireNonNull(viewModel.getWasContinueClicked().getValue()); - if (isResumed && continueClicked && areEssentialPermissionsGranted()) { - if (viewModel.isWifiReady() && viewModel.isBluetoothReady()) { - LOG.info("Wifi and Bluetooth are ready"); - viewModel.startAddingContact(); + @Override + public void onActivityResult(int request, int result, + @Nullable Intent data) { + if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { + if (result == RESULT_CANCELED) { + LOG.info("Bluetooth discoverability was refused"); + viewModel.bluetoothDecision = REFUSED; } else { - viewModel.enableWifiIfWeShould(); - if (viewModel.bluetoothDecision == UNKNOWN) { - requestBluetoothDiscoverable(); - } else if (viewModel.bluetoothDecision == REFUSED) { - // Ask again when the user clicks "continue" - } else { - viewModel.enableBluetoothIfWeShould(); - } + LOG.info("Bluetooth discoverability was accepted"); + viewModel.bluetoothDecision = ACCEPTED; } - } + showQrCodeFragmentIfAllowed(); + } else super.onActivityResult(request, result, data); } - private boolean areEssentialPermissionsGranted() { - return cameraPermission == Permission.GRANTED && - (SDK_INT < 23 || locationPermission == Permission.GRANTED || - !viewModel.isBluetoothSupported()); + @Override + @UiThread + public void onRequestPermissionsResult(int requestCode, + String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, + grantResults); + permissionManager.onRequestPermissionsResult(requestCode, permissions, + grantResults, this::showQrCodeFragmentIfAllowed); } private void requestBluetoothDiscoverable() { @@ -190,19 +171,27 @@ public abstract class KeyAgreementActivity extends BriarActivity } } - @Override - public void onActivityResult(int request, int result, - @Nullable Intent data) { - if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { - if (result == RESULT_CANCELED) { - LOG.info("Bluetooth discoverability was refused"); - viewModel.bluetoothDecision = REFUSED; + @SuppressWarnings("StatementWithEmptyBody") + private void showQrCodeFragmentIfAllowed() { + boolean continueClicked = // never set to null + requireNonNull(viewModel.getWasContinueClicked().getValue()); + boolean permissionsGranted = + permissionManager.areEssentialPermissionsGranted(); + if (isResumed && continueClicked && permissionsGranted) { + if (viewModel.isWifiReady() && viewModel.isBluetoothReady()) { + LOG.info("Wifi and Bluetooth are ready"); + viewModel.startAddingContact(); } else { - LOG.info("Bluetooth discoverability was accepted"); - viewModel.bluetoothDecision = ACCEPTED; + viewModel.enableWifiIfWeShould(); + if (viewModel.bluetoothDecision == UNKNOWN) { + requestBluetoothDiscoverable(); + } else if (viewModel.bluetoothDecision == REFUSED) { + // Ask again when the user clicks "continue" + } else { + viewModel.enableBluetoothIfWeShould(); + } } - showQrCodeFragmentIfAllowed(); - } else super.onActivityResult(request, result, data); + } } private void showQrCodeFragment() { @@ -217,118 +206,7 @@ public abstract class KeyAgreementActivity extends BriarActivity } } - private boolean checkPermissions() { - if (areEssentialPermissionsGranted()) return true; - // If an essential permission has been permanently denied, ask the - // user to change the setting - if (cameraPermission == Permission.PERMANENTLY_DENIED) { - showDenialDialog(R.string.permission_camera_title, - R.string.permission_camera_denied_body); - return false; - } - if (viewModel.isBluetoothSupported() && - locationPermission == Permission.PERMANENTLY_DENIED) { - showDenialDialog(R.string.permission_location_title, - R.string.permission_location_denied_body); - return false; - } - // Should we show the rationale for one or both permissions? - if (cameraPermission == Permission.SHOW_RATIONALE && - locationPermission == Permission.SHOW_RATIONALE) { - showRationale(R.string.permission_camera_location_title, - R.string.permission_camera_location_request_body); - } else if (cameraPermission == Permission.SHOW_RATIONALE) { - showRationale(R.string.permission_camera_title, - R.string.permission_camera_request_body); - } else if (locationPermission == Permission.SHOW_RATIONALE) { - showRationale(R.string.permission_location_title, - R.string.permission_location_request_body); - } else { - requestPermissions(); - } - return false; - } - - private void showDenialDialog(@StringRes int title, @StringRes int body) { - Builder builder = new Builder(this, R.style.BriarDialogTheme); - builder.setTitle(title); - builder.setMessage(body); - builder.setPositiveButton(R.string.ok, getGoToSettingsListener(this)); - builder.setNegativeButton(R.string.cancel, - (dialog, which) -> supportFinishAfterTransition()); - builder.show(); - } - - private void showRationale(@StringRes int title, @StringRes int body) { - Builder builder = new Builder(this, R.style.BriarDialogTheme); - builder.setTitle(title); - builder.setMessage(body); - builder.setNeutralButton(R.string.continue_button, - (dialog, which) -> requestPermissions()); - builder.show(); - } - - private void requestPermissions() { - String[] permissions; - if (viewModel.isBluetoothSupported()) { - permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION}; - } else { - permissions = new String[] {CAMERA}; - } - ActivityCompat.requestPermissions(this, permissions, - REQUEST_PERMISSION_CAMERA_LOCATION); - } - - @Override - @UiThread - public void onRequestPermissionsResult(int requestCode, - String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, - grantResults); - if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION) - throw new AssertionError(); - if (gotPermission(CAMERA, permissions, grantResults)) { - cameraPermission = Permission.GRANTED; - } else if (shouldShowRationale(CAMERA)) { - cameraPermission = Permission.SHOW_RATIONALE; - } else { - cameraPermission = Permission.PERMANENTLY_DENIED; - } - if (viewModel.isBluetoothSupported()) { - if (gotPermission(ACCESS_FINE_LOCATION, permissions, - grantResults)) { - locationPermission = Permission.GRANTED; - } else if (shouldShowRationale(ACCESS_FINE_LOCATION)) { - locationPermission = Permission.SHOW_RATIONALE; - } else { - locationPermission = Permission.PERMANENTLY_DENIED; - } - } - // If a permission dialog has been shown, showing the QR code fragment - // on this call path would cause a crash due to - // https://code.google.com/p/android/issues/detail?id=190966. - // In that case the isResumed flag prevents the fragment from being - // shown here, and showQrCodeFragmentIfAllowed() will be called again - // from onPostResume(). - if (checkPermissions()) showQrCodeFragmentIfAllowed(); - } - - private boolean gotPermission(String permission, String[] permissions, - int[] grantResults) { - for (int i = 0; i < permissions.length; i++) { - if (permission.equals(permissions[i])) - return grantResults[i] == PERMISSION_GRANTED; - } - return false; - } - - private boolean shouldShowRationale(String permission) { - return ActivityCompat.shouldShowRequestPermissionRationale(this, - permission); - } - private class BluetoothStateReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { LOG.info("Bluetooth scan mode changed"); From bcc0442added2fac57c07b5304dfb0ef0253b88b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 4 Feb 2021 14:31:41 -0300 Subject: [PATCH 08/21] Merge activities for adding contact nearby and rename related classes to consolidate names --- briar-android/src/main/AndroidManifest.xml | 2 +- .../briarproject/briar/android/AppModule.java | 4 +- .../android/activity/ActivityComponent.java | 21 ++- .../android/contact/ContactListFragment.java | 5 +- .../contact/add/nearby/AddContactState.java | 76 ++++++++++ ...ity.java => AddNearbyContactActivity.java} | 130 ++++++++++++++---- ...ava => AddNearbyContactErrorFragment.java} | 10 +- ...ent.java => AddNearbyContactFragment.java} | 30 ++-- ...ava => AddNearbyContactIntroFragment.java} | 13 +- ...odule.java => AddNearbyContactModule.java} | 6 +- ...el.java => AddNearbyContactViewModel.java} | 38 ++--- .../add/nearby/ContactAddingState.java | 55 -------- .../add/nearby/ContactExchangeActivity.java | 101 -------------- .../add/nearby/ContactExchangeResult.java | 30 ---- 14 files changed, 245 insertions(+), 276 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java rename briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/{KeyAgreementActivity.java => AddNearbyContactActivity.java} (63%) rename briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/{ContactExchangeErrorFragment.java => AddNearbyContactErrorFragment.java} (88%) rename briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/{KeyAgreementFragment.java => AddNearbyContactFragment.java} (84%) rename briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/{IntroFragment.java => AddNearbyContactIntroFragment.java} (81%) rename briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/{ContactExchangeModule.java => AddNearbyContactModule.java} (67%) rename briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/{ContactExchangeViewModel.java => AddNearbyContactViewModel.java} (91%) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 693af657f..87d431f37 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -342,7 +342,7 @@ diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 08057a229..05166d043 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -31,7 +31,7 @@ import org.briarproject.briar.android.account.DozeHelperModule; import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.SetupModule; import org.briarproject.briar.android.contact.ContactListModule; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeModule; +import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule; import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.introduction.IntroductionModule; import org.briarproject.briar.android.logging.LoggingModule; @@ -75,7 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; @Module(includes = { SetupModule.class, DozeHelperModule.class, - ContactExchangeModule.class, + AddNearbyContactModule.class, LoggingModule.class, LoginModule.class, NavDrawerModule.class, 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 18a9d26f8..1ce77a0ca 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 @@ -21,11 +21,10 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.contact.ContactListFragment; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeErrorFragment; -import org.briarproject.briar.android.contact.add.nearby.IntroFragment; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementActivity; -import org.briarproject.briar.android.contact.add.nearby.KeyAgreementFragment; +import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactActivity; +import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactErrorFragment; +import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactFragment; +import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactIntroFragment; import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment; @@ -109,9 +108,7 @@ public interface ActivityComponent { void inject(PanicPreferencesActivity activity); - void inject(ContactExchangeActivity activity); - - void inject(KeyAgreementActivity activity); + void inject(AddNearbyContactActivity activity); void inject(ConversationActivity activity); @@ -209,9 +206,9 @@ public interface ActivityComponent { void inject(FeedFragment fragment); - void inject(IntroFragment fragment); - - void inject(KeyAgreementFragment fragment); + void inject(AddNearbyContactIntroFragment fragment); + + void inject(AddNearbyContactFragment fragment); void inject(LinkExchangeFragment fragment); @@ -229,7 +226,7 @@ public interface ActivityComponent { void inject(ScreenFilterDialogFragment fragment); - void inject(ContactExchangeErrorFragment fragment); + void inject(AddNearbyContactErrorFragment fragment); void inject(AliasDialogFragment aliasDialogFragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java index 48c3935ae..380deb968 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java @@ -15,11 +15,11 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; 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.AddNearbyContactActivity; import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.fragment.BaseFragment; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeActivity; import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.view.BriarRecyclerView; @@ -125,7 +125,8 @@ public class ContactListFragment extends BaseFragment switch (itemId) { case R.id.action_add_contact_nearby: Intent intent = - new Intent(getContext(), ContactExchangeActivity.class); + new Intent(getContext(), + AddNearbyContactActivity.class); startActivity(intent); return; case R.id.action_add_contact_remotely: 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 new file mode 100644 index 000000000..df56be6bb --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddContactState.java @@ -0,0 +1,76 @@ +package org.briarproject.briar.android.contact.add.nearby; + +import android.graphics.Bitmap; + +import org.briarproject.bramble.api.identity.Author; + +import androidx.annotation.Nullable; + +abstract class AddContactState { + + static class KeyAgreementListening extends AddContactState { + final Bitmap qrCode; + + KeyAgreementListening(Bitmap qrCode) { + this.qrCode = qrCode; + } + } + + static class QrCodeScanned extends AddContactState { + } + + static class KeyAgreementWaiting extends AddContactState { + } + + static class KeyAgreementStarted extends AddContactState { + } + + static class ContactExchangeStarted extends AddContactState { + } + + static class ContactExchangeFinished extends AddContactState { + final ContactExchangeResult result; + + ContactExchangeFinished(ContactExchangeResult result) { + this.result = result; + } + } + + 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; + } + + Failed() { + this(null); + } + } + + abstract static class ContactExchangeResult { + static class Success extends ContactExchangeResult { + final Author remoteAuthor; + + Success(Author remoteAuthor) { + this.remoteAuthor = remoteAuthor; + } + } + + static class Error extends ContactExchangeResult { + @Nullable + final Author duplicateAuthor; + + Error(@Nullable Author duplicateAuthor) { + this.duplicateAuthor = duplicateAuthor; + } + } + } // end ContactExchangeResult + +} 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/AddNearbyContactActivity.java similarity index 63% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java index 6c8b76a41..eb56baeeb 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/AddNearbyContactActivity.java @@ -6,13 +6,19 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.view.MenuItem; +import android.widget.Toast; +import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NullSafety; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; 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.ContactExchangeViewModel.BluetoothDecision; +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.AddNearbyContactViewModel.BluetoothDecision; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; @@ -21,32 +27,32 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; -import androidx.annotation.UiThread; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; +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.nullsafety.NullSafety.requireNonNull; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; -import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.ACCEPTED; -import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; -import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; @MethodsNotNullByDefault @ParametersNotNullByDefault -public abstract class KeyAgreementActivity extends BriarActivity +public class AddNearbyContactActivity extends BriarActivity implements BaseFragmentListener { private static final Logger LOG = - getLogger(KeyAgreementActivity.class.getName()); + getLogger(AddNearbyContactActivity.class.getName()); @Inject ViewModelProvider.Factory viewModelFactory; - protected ContactExchangeViewModel viewModel; + private AddNearbyContactViewModel viewModel; private AddNearbyContactPermissionManager permissionManager; /** @@ -62,7 +68,7 @@ public abstract class KeyAgreementActivity extends BriarActivity public void injectActivity(ActivityComponent component) { component.inject(this); viewModel = new ViewModelProvider(this, viewModelFactory) - .get(ContactExchangeViewModel.class); + .get(AddNearbyContactViewModel.class); permissionManager = new AddNearbyContactPermissionManager(this, viewModel.isBluetoothSupported()); } @@ -73,9 +79,10 @@ public abstract class KeyAgreementActivity extends BriarActivity setContentView(R.layout.activity_fragment_container_toolbar); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); + NullSafety.requireNonNull(getSupportActionBar()) + .setDisplayHomeAsUpEnabled(true); if (state == null) { - showInitialFragment(IntroFragment.newInstance()); + showInitialFragment(AddNearbyContactIntroFragment.newInstance()); } IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); bluetoothReceiver = new BluetoothStateReceiver(); @@ -90,6 +97,10 @@ public abstract class KeyAgreementActivity extends BriarActivity viewModel.getShowQrCodeFragment().observeEvent(this, show -> { if (show) showQrCodeFragment(); }); + requireNonNull(getSupportActionBar()) + .setTitle(R.string.add_contact_title); + viewModel.getState() + .observe(this, this::onAddContactStateChanged); } @Override @@ -120,15 +131,6 @@ public abstract class KeyAgreementActivity extends BriarActivity if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - return super.onOptionsItemSelected(item); - } - @Override public void onActivityResult(int request, int result, @Nullable Intent data) { @@ -145,7 +147,15 @@ public abstract class KeyAgreementActivity extends BriarActivity } @Override - @UiThread + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, @@ -154,6 +164,16 @@ public abstract class KeyAgreementActivity extends BriarActivity grantResults, this::showQrCodeFragmentIfAllowed); } + @Override + public void onBackPressed() { + if (viewModel.getState().getValue() instanceof Failed) { + // finish this activity when going back in failed state + supportFinishAfterTransition(); + } else { + super.onBackPressed(); + } + } + private void requestBluetoothDiscoverable() { if (!viewModel.isBluetoothSupported()) { viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; @@ -174,7 +194,8 @@ public abstract class KeyAgreementActivity extends BriarActivity @SuppressWarnings("StatementWithEmptyBody") private void showQrCodeFragmentIfAllowed() { boolean continueClicked = // never set to null - requireNonNull(viewModel.getWasContinueClicked().getValue()); + NullSafety.requireNonNull( + viewModel.getWasContinueClicked().getValue()); boolean permissionsGranted = permissionManager.areEssentialPermissionsGranted(); if (isResumed && continueClicked && permissionsGranted) { @@ -197,8 +218,8 @@ public abstract class KeyAgreementActivity extends BriarActivity private void showQrCodeFragment() { // FIXME #824 FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { - BaseFragment f = KeyAgreementFragment.newInstance(); + if (fm.findFragmentByTag(AddNearbyContactFragment.TAG) == null) { + BaseFragment f = AddNearbyContactFragment.newInstance(); fm.beginTransaction() .replace(R.id.fragmentContainer, f, f.getUniqueTag()) .addToBackStack(f.getUniqueTag()) @@ -206,6 +227,65 @@ public abstract class KeyAgreementActivity extends BriarActivity } } + private void onAddContactStateChanged(AddContactState state) { + if (state instanceof ContactExchangeFinished) { + ContactExchangeResult result = + ((ContactExchangeFinished) state).result; + onContactExchangeResult(result); + } else if (state instanceof Failed) { + // Remove navigation icon, so user can't go back when failed + // ErrorFragment will finish or relaunch this activity + Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setNavigationIcon(null); + + Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; + onAddingContactFailed(qrCodeTooOld); + } + } + + 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 onAddingContactFailed(@Nullable Boolean qrCodeTooOld) { + if (qrCodeTooOld == null) { + showErrorFragment(); + } else { + String msg; + if (qrCodeTooOld) { + msg = getString(R.string.qr_code_too_old, + getString(R.string.app_name)); + } else { + msg = getString(R.string.qr_code_too_new, + getString(R.string.app_name)); + } + showNextFragment(AddNearbyContactErrorFragment.newInstance(msg)); + } + } + + private void showErrorFragment() { + showNextFragment(new AddNearbyContactErrorFragment()); + } + private class BluetoothStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java similarity index 88% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeErrorFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java index 6a73ed8a8..15dc04848 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactErrorFragment.java @@ -24,14 +24,14 @@ import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class ContactExchangeErrorFragment extends BaseFragment { +public class AddNearbyContactErrorFragment extends BaseFragment { public static final String TAG = - ContactExchangeErrorFragment.class.getName(); + AddNearbyContactErrorFragment.class.getName(); private static final String ERROR_MSG = "errorMessage"; - public static ContactExchangeErrorFragment newInstance(String errorMsg) { - ContactExchangeErrorFragment f = new ContactExchangeErrorFragment(); + public static AddNearbyContactErrorFragment newInstance(String errorMsg) { + AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment(); Bundle args = new Bundle(); args.putString(ERROR_MSG, errorMsg); f.setArguments(args); @@ -72,7 +72,7 @@ public class ContactExchangeErrorFragment extends BaseFragment { tryAgain.setOnClickListener(view -> { // Recreate the activity so we return to the intro fragment FragmentActivity activity = requireActivity(); - Intent i = new Intent(activity, ContactExchangeActivity.class); + Intent i = new Intent(activity, AddNearbyContactActivity.class); i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); activity.startActivity(i); }); 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/AddNearbyContactFragment.java similarity index 84% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/KeyAgreementFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java index 2aa53fd50..c08a8cdc9 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/AddNearbyContactFragment.java @@ -14,11 +14,11 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; 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.ContactAddingState.ContactExchangeStarted; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.Failed; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementStarted; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.QrCodeScanned; +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.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.fragment.BaseFragment; import org.briarproject.briar.android.view.QrCodeView; @@ -42,26 +42,26 @@ import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class KeyAgreementFragment extends BaseFragment +public class AddNearbyContactFragment extends BaseFragment implements QrCodeView.FullscreenListener { - static final String TAG = KeyAgreementFragment.class.getName(); + static final String TAG = AddNearbyContactFragment.class.getName(); private static final Logger LOG = Logger.getLogger(TAG); @Inject ViewModelProvider.Factory viewModelFactory; - private ContactExchangeViewModel viewModel; + private AddNearbyContactViewModel viewModel; private CameraView cameraView; private LinearLayout cameraOverlay; private View statusView; private QrCodeView qrCodeView; private TextView status; - public static KeyAgreementFragment newInstance() { + public static AddNearbyContactFragment newInstance() { Bundle args = new Bundle(); - KeyAgreementFragment fragment = new KeyAgreementFragment(); + AddNearbyContactFragment fragment = new AddNearbyContactFragment(); fragment.setArguments(args); return fragment; } @@ -70,7 +70,7 @@ public class KeyAgreementFragment extends BaseFragment public void injectFragment(ActivityComponent component) { component.inject(this); viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) - .get(ContactExchangeViewModel.class); + .get(AddNearbyContactViewModel.class); } @Nullable @@ -93,7 +93,7 @@ public class KeyAgreementFragment extends BaseFragment qrCodeView.setFullscreenListener(this); viewModel.getState().observe(getViewLifecycleOwner(), - this::onContactAddingStateChanged); + this::onAddContactStateChanged); } @Override @@ -153,10 +153,10 @@ public class KeyAgreementFragment extends BaseFragment } @UiThread - private void onContactAddingStateChanged(ContactAddingState state) { - if (state instanceof ContactAddingState.KeyAgreementListening) { + private void onAddContactStateChanged(AddContactState state) { + if (state instanceof AddContactState.KeyAgreementListening) { Bitmap qrCode = - ((ContactAddingState.KeyAgreementListening) state).qrCode; + ((AddContactState.KeyAgreementListening) state).qrCode; qrCodeView.setQrCode(qrCode); } else if (state instanceof QrCodeScanned) { try { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java similarity index 81% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java index 4b7a6201f..7bc666550 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/IntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java @@ -21,20 +21,21 @@ import static android.view.View.FOCUS_DOWN; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class IntroFragment extends BaseFragment { +public class AddNearbyContactIntroFragment extends BaseFragment { - public static final String TAG = IntroFragment.class.getName(); + public static final String TAG = AddNearbyContactIntroFragment.class.getName(); @Inject ViewModelProvider.Factory viewModelFactory; - private ContactExchangeViewModel viewModel; + private AddNearbyContactViewModel viewModel; private ScrollView scrollView; - public static IntroFragment newInstance() { + public static AddNearbyContactIntroFragment newInstance() { Bundle args = new Bundle(); - IntroFragment fragment = new IntroFragment(); + AddNearbyContactIntroFragment + fragment = new AddNearbyContactIntroFragment(); fragment.setArguments(args); return fragment; } @@ -43,7 +44,7 @@ public class IntroFragment extends BaseFragment { public void injectFragment(ActivityComponent component) { component.inject(this); viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) - .get(ContactExchangeViewModel.class); + .get(AddNearbyContactViewModel.class); } @Nullable diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeModule.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactModule.java similarity index 67% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeModule.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactModule.java index bdd7fd2ee..79dfaa69c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactModule.java @@ -8,12 +8,12 @@ import dagger.Module; import dagger.multibindings.IntoMap; @Module -public abstract class ContactExchangeModule { +public abstract class AddNearbyContactModule { @Binds @IntoMap - @ViewModelKey(ContactExchangeViewModel.class) + @ViewModelKey(AddNearbyContactViewModel.class) abstract ViewModel bindContactExchangeViewModel( - ContactExchangeViewModel contactExchangeViewModel); + AddNearbyContactViewModel addNearbyContactViewModel); } 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/AddNearbyContactViewModel.java similarity index 91% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeViewModel.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java index 0b0d11ec2..f5f9da9ee 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/AddNearbyContactViewModel.java @@ -40,13 +40,13 @@ 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.briar.R; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeStarted; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementListening; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementStarted; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error; -import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success; +import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished; +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.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.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent; @@ -75,15 +75,15 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.util.LogUtils.logException; -import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.REFUSED; -import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.BluetoothDecision.UNKNOWN; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; @NotNullByDefault -class ContactExchangeViewModel extends AndroidViewModel +class AddNearbyContactViewModel extends AndroidViewModel implements EventListener, QrCodeDecoder.ResultCallback { private static final Logger LOG = - getLogger(ContactExchangeViewModel.class.getName()); + getLogger(AddNearbyContactViewModel.class.getName()); enum BluetoothDecision { /** @@ -135,7 +135,7 @@ class ContactExchangeViewModel extends AndroidViewModel new MutableLiveEvent<>(); private final MutableLiveEvent transportStateChanged = new MutableLiveEvent<>(); - private final MutableLiveData state = + private final MutableLiveData state = new MutableLiveData<>(); final QrCodeDecoder qrCodeDecoder; @@ -164,7 +164,7 @@ class ContactExchangeViewModel extends AndroidViewModel private volatile boolean gotLocalPayload = false, gotRemotePayload = false; @Inject - ContactExchangeViewModel(Application app, + AddNearbyContactViewModel(Application app, EventBus eventBus, @IoExecutor Executor ioExecutor, PluginManager pluginManager, @@ -336,11 +336,11 @@ class ContactExchangeViewModel extends AndroidViewModel } else if (e instanceof KeyAgreementAbortedEvent) { LOG.info("KeyAgreementAbortedEvent received"); resetPayloadFlags(); - state.setValue(new ContactAddingState.Failed()); + state.setValue(new AddContactState.Failed()); } else if (e instanceof KeyAgreementFailedEvent) { LOG.info("KeyAgreementFailedEvent received"); resetPayloadFlags(); - state.setValue(new ContactAddingState.Failed()); + state.setValue(new AddContactState.Failed()); } } @@ -377,16 +377,16 @@ class ContactExchangeViewModel extends AndroidViewModel Payload remotePayload = payloadParser.parse(payloadBytes); gotRemotePayload = true; requireNonNull(task).connectAndRunProtocol(remotePayload); - state.postValue(new ContactAddingState.QrCodeScanned()); + state.postValue(new AddContactState.QrCodeScanned()); } catch (UnsupportedVersionException e) { resetPayloadFlags(); - state.postValue(new ContactAddingState.Failed(e.isTooOld())); + state.postValue(new AddContactState.Failed(e.isTooOld())); } catch (IOException | IllegalArgumentException e) { LOG.log(WARNING, "QR Code Invalid", e); Toast.makeText(getApplication(), R.string.qr_code_invalid, LENGTH_LONG).show(); resetPayloadFlags(); - state.postValue(new ContactAddingState.Failed()); + state.postValue(new AddContactState.Failed()); } } @@ -448,7 +448,7 @@ class ContactExchangeViewModel extends AndroidViewModel return showQrCodeFragment; } - LiveData getState() { + LiveData getState() { return state; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java deleted file mode 100644 index 51278b322..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactAddingState.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.briarproject.briar.android.contact.add.nearby; - -import android.graphics.Bitmap; - -import androidx.annotation.Nullable; - -abstract class ContactAddingState { - - static class KeyAgreementListening extends ContactAddingState { - final Bitmap qrCode; - - KeyAgreementListening(Bitmap qrCode) { - this.qrCode = qrCode; - } - } - - static class QrCodeScanned extends ContactAddingState { - } - - static class KeyAgreementWaiting extends ContactAddingState { - } - - static class KeyAgreementStarted extends ContactAddingState { - } - - static class ContactExchangeStarted extends ContactAddingState { - } - - static class ContactExchangeFinished extends ContactAddingState { - final ContactExchangeResult result; - - ContactExchangeFinished(ContactExchangeResult result) { - this.result = result; - } - } - - static class Failed extends ContactAddingState { - /** - * 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; - } - - Failed() { - this(null); - } - } - -} 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 deleted file mode 100644 index c4a435136..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeActivity.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.briarproject.briar.android.contact.add.nearby; - -import android.os.Bundle; -import android.widget.Toast; - -import org.briarproject.bramble.api.identity.Author; -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.ContactAddingState.ContactExchangeFinished; -import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.Failed; - -import javax.annotation.Nullable; - -import androidx.appcompat.widget.Toolbar; - -import static android.widget.Toast.LENGTH_LONG; -import static java.util.Objects.requireNonNull; - -@MethodsNotNullByDefault -@ParametersNotNullByDefault -public class ContactExchangeActivity extends KeyAgreementActivity { - - @Override - public void onCreate(@Nullable Bundle state) { - super.onCreate(state); - requireNonNull(getSupportActionBar()) - .setTitle(R.string.add_contact_title); - viewModel.getState() - .observe(this, this::onContactAddingStateChanged); - } - - @Override - public void onBackPressed() { - if (viewModel.getState().getValue() instanceof Failed) { - // finish this activity when going back in failed state - supportFinishAfterTransition(); - } else { - super.onBackPressed(); - } - } - - private void onContactAddingStateChanged(ContactAddingState state) { - if (state instanceof ContactExchangeFinished) { - ContactExchangeResult result = - ((ContactExchangeFinished) state).result; - onContactExchangeResult(result); - } else if (state instanceof Failed) { - // Remove navigation icon, so user can't go back when failed - // ErrorFragment will finish or relaunch this activity - Toolbar toolbar = findViewById(R.id.toolbar); - toolbar.setNavigationIcon(null); - - Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; - onAddingContactFailed(qrCodeTooOld); - } - } - - 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 onAddingContactFailed(@Nullable Boolean qrCodeTooOld) { - if (qrCodeTooOld == null) { - showErrorFragment(); - } else { - String msg; - if (qrCodeTooOld) { - msg = getString(R.string.qr_code_too_old, - getString(R.string.app_name)); - } else { - msg = getString(R.string.qr_code_too_new, - getString(R.string.app_name)); - } - showNextFragment(ContactExchangeErrorFragment.newInstance(msg)); - } - } - - private void showErrorFragment() { - showNextFragment(new ContactExchangeErrorFragment()); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java deleted file mode 100644 index dad8a2a87..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/ContactExchangeResult.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.briarproject.briar.android.contact.add.nearby; - -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -@Immutable -@NotNullByDefault -abstract class ContactExchangeResult { - - static class Success extends ContactExchangeResult { - final Author remoteAuthor; - - Success(Author remoteAuthor) { - this.remoteAuthor = remoteAuthor; - } - } - - static class Error extends ContactExchangeResult { - @Nullable - final Author duplicateAuthor; - - Error(@Nullable Author duplicateAuthor) { - this.duplicateAuthor = duplicateAuthor; - } - } - -} From 7f486eef4c3e0ec52a39c5a7d37757b88bab9910 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 23 Mar 2021 17:00:36 -0300 Subject: [PATCH 09/21] Refactor more code into AddNearbyContactViewModel thus concentrating the logic there needing less back and forth with the activity --- .../briar/android/activity/RequestCodes.java | 1 - .../add/nearby/AddNearbyContactActivity.java | 126 +++++------------- .../add/nearby/AddNearbyContactFragment.java | 2 - .../AddNearbyContactPermissionManager.java | 16 ++- .../add/nearby/AddNearbyContactViewModel.java | 113 ++++++++++++---- .../contact/add/nearby/QrCodeDecoder.java | 8 +- .../util/RequestBluetoothDiscoverable.java | 31 +++++ .../activity_fragment_container_toolbar.xml | 2 +- 8 files changed, 170 insertions(+), 129 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/util/RequestBluetoothDiscoverable.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index 58ab2ef0c..de989877f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -11,7 +11,6 @@ public interface RequestCodes { int REQUEST_RINGTONE = 7; int REQUEST_PERMISSION_CAMERA_LOCATION = 8; int REQUEST_DOZE_WHITELISTING = 9; - int REQUEST_BLUETOOTH_DISCOVERABLE = 10; int REQUEST_UNLOCK = 11; int REQUEST_KEYGUARD_UNLOCK = 12; int REQUEST_ATTACH_IMAGE = 13; 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 eb56baeeb..126590ae1 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 @@ -1,16 +1,12 @@ package org.briarproject.briar.android.contact.add.nearby; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import android.view.MenuItem; import android.widget.Toast; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; -import org.briarproject.bramble.api.nullsafety.NullSafety; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; @@ -21,25 +17,25 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed; 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; +import org.briarproject.briar.android.util.RequestBluetoothDiscoverable; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; +import androidx.activity.result.ActivityResultLauncher; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; -import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; +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.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; -import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -55,14 +51,9 @@ public class AddNearbyContactActivity extends BriarActivity private AddNearbyContactViewModel viewModel; private AddNearbyContactPermissionManager permissionManager; - /** - * Set to true in onPostResume() and false in onPause(). This prevents the - * QR code fragment from being shown if onRequestPermissionsResult() is - * called while the activity is paused, which could cause a crash due to - * https://issuetracker.google.com/issues/37067655. - */ - private boolean isResumed = false; - private BroadcastReceiver bluetoothReceiver = null; + private final ActivityResultLauncher bluetoothLauncher = + registerForActivityResult(new RequestBluetoothDiscoverable(), + this::onBluetoothDiscoverableResult); @Override public void injectActivity(ActivityComponent component) { @@ -79,21 +70,14 @@ public class AddNearbyContactActivity extends BriarActivity setContentView(R.layout.activity_fragment_container_toolbar); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - NullSafety.requireNonNull(getSupportActionBar()) - .setDisplayHomeAsUpEnabled(true); + requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); if (state == null) { showInitialFragment(AddNearbyContactIntroFragment.newInstance()); } - IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); - bluetoothReceiver = new BluetoothStateReceiver(); - registerReceiver(bluetoothReceiver, filter); - viewModel.getWasContinueClicked().observe(this, clicked -> { - if (clicked && permissionManager.checkPermissions()) { - showQrCodeFragmentIfAllowed(); - } - }); - viewModel.getTransportStateChanged().observeEvent(this, - t -> showQrCodeFragmentIfAllowed()); + viewModel.getCheckPermissions().observeEvent(this, check -> + permissionManager.checkPermissions()); // never false + viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r -> + requestBluetoothDiscoverable()); // never false viewModel.getShowQrCodeFragment().observeEvent(this, show -> { if (show) showQrCodeFragment(); }); @@ -113,37 +97,23 @@ public class AddNearbyContactActivity extends BriarActivity @Override protected void onPostResume() { super.onPostResume(); - isResumed = true; - // Workaround for - // https://code.google.com/p/android/issues/detail?id=190966 - showQrCodeFragmentIfAllowed(); + viewModel.setIsActivityResumed(true); } @Override protected void onPause() { super.onPause(); - isResumed = false; + viewModel.setIsActivityResumed(false); } - @Override - public void onDestroy() { - super.onDestroy(); - if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); - } - - @Override - public void onActivityResult(int request, int result, - @Nullable Intent data) { - if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { - if (result == RESULT_CANCELED) { - LOG.info("Bluetooth discoverability was refused"); - viewModel.bluetoothDecision = REFUSED; - } else { - LOG.info("Bluetooth discoverability was accepted"); - viewModel.bluetoothDecision = ACCEPTED; - } - showQrCodeFragmentIfAllowed(); - } else super.onActivityResult(request, result, data); + private void onBluetoothDiscoverableResult(boolean discoverable) { + if (discoverable) { + LOG.info("Bluetooth discoverability was accepted"); + viewModel.setBluetoothDecision(ACCEPTED); + } else { + LOG.info("Bluetooth discoverability was refused"); + viewModel.setBluetoothDecision(REFUSED); + } } @Override @@ -161,14 +131,16 @@ public class AddNearbyContactActivity extends BriarActivity super.onRequestPermissionsResult(requestCode, permissions, grantResults); permissionManager.onRequestPermissionsResult(requestCode, permissions, - grantResults, this::showQrCodeFragmentIfAllowed); + grantResults, viewModel::showQrCodeFragmentIfAllowed); } @Override public void onBackPressed() { if (viewModel.getState().getValue() instanceof Failed) { - // finish this activity when going back in failed state - supportFinishAfterTransition(); + // re-create this activity when going back in failed state + Intent i = new Intent(this, AddNearbyContactActivity.class); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); } else { super.onBackPressed(); } @@ -176,41 +148,15 @@ public class AddNearbyContactActivity extends BriarActivity private void requestBluetoothDiscoverable() { if (!viewModel.isBluetoothSupported()) { - viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; - showQrCodeFragmentIfAllowed(); + viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER); } else { Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); if (i.resolveActivity(getPackageManager()) != null) { LOG.info("Asking for Bluetooth discoverability"); - viewModel.bluetoothDecision = BluetoothDecision.WAITING; - startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE); + viewModel.setBluetoothDecision(BluetoothDecision.WAITING); + bluetoothLauncher.launch(120); // 2min discoverable } else { - viewModel.bluetoothDecision = BluetoothDecision.NO_ADAPTER; - showQrCodeFragmentIfAllowed(); - } - } - } - - @SuppressWarnings("StatementWithEmptyBody") - private void showQrCodeFragmentIfAllowed() { - boolean continueClicked = // never set to null - NullSafety.requireNonNull( - viewModel.getWasContinueClicked().getValue()); - boolean permissionsGranted = - permissionManager.areEssentialPermissionsGranted(); - if (isResumed && continueClicked && permissionsGranted) { - if (viewModel.isWifiReady() && viewModel.isBluetoothReady()) { - LOG.info("Wifi and Bluetooth are ready"); - viewModel.startAddingContact(); - } else { - viewModel.enableWifiIfWeShould(); - if (viewModel.bluetoothDecision == UNKNOWN) { - requestBluetoothDiscoverable(); - } else if (viewModel.bluetoothDecision == REFUSED) { - // Ask again when the user clicks "continue" - } else { - viewModel.enableBluetoothIfWeShould(); - } + viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER); } } } @@ -233,11 +179,6 @@ public class AddNearbyContactActivity extends BriarActivity ((ContactExchangeFinished) state).result; onContactExchangeResult(result); } else if (state instanceof Failed) { - // Remove navigation icon, so user can't go back when failed - // ErrorFragment will finish or relaunch this activity - Toolbar toolbar = findViewById(R.id.toolbar); - toolbar.setNavigationIcon(null); - Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; onAddingContactFailed(qrCodeTooOld); } @@ -286,11 +227,4 @@ public class AddNearbyContactActivity extends BriarActivity showNextFragment(new AddNearbyContactErrorFragment()); } - private class BluetoothStateReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - LOG.info("Bluetooth scan mode changed"); - showQrCodeFragmentIfAllowed(); - } - } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java index c08a8cdc9..6f0a841cb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java @@ -171,10 +171,8 @@ public class AddNearbyContactFragment extends BaseFragment status.setText(R.string.waiting_for_contact_to_scan); } else if (state instanceof KeyAgreementStarted) { qrCodeView.setVisibility(INVISIBLE); - statusView.setVisibility(VISIBLE); status.setText(R.string.authenticating_with_device); } else if (state instanceof ContactExchangeStarted) { - statusView.setVisibility(VISIBLE); status.setText(R.string.exchanging_contact_details); } else if (state instanceof Failed) { // the activity will replace this fragment with an error fragment diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java index 1244da3ae..937799e10 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -1,5 +1,7 @@ package org.briarproject.briar.android.contact.add.nearby; +import android.content.Context; + import org.briarproject.briar.R; import org.briarproject.briar.android.activity.BaseActivity; @@ -11,6 +13,8 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.CAMERA; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; +import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; +import static androidx.core.content.ContextCompat.checkSelfPermission; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; @@ -37,6 +41,15 @@ class AddNearbyContactPermissionManager { locationPermission = Permission.UNKNOWN; } + static boolean areEssentialPermissionsGranted(Context ctx, + boolean isBluetoothSupported) { + int ok = PERMISSION_GRANTED; + return checkSelfPermission(ctx, CAMERA) == ok && + (SDK_INT < 23 || + checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == ok || + !isBluetoothSupported); + } + boolean areEssentialPermissionsGranted() { return cameraPermission == Permission.GRANTED && (SDK_INT < 23 || locationPermission == Permission.GRANTED || @@ -147,8 +160,7 @@ class AddNearbyContactPermissionManager { } private boolean shouldShowRationale(String permission) { - return ActivityCompat - .shouldShowRequestPermissionRationale(ctx, permission); + return shouldShowRequestPermissionRationale(ctx, permission); } } 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 f5f9da9ee..46182ce29 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 @@ -2,6 +2,10 @@ package org.briarproject.briar.android.contact.add.nearby; import android.app.Application; import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Bitmap; import android.util.DisplayMetrics; import android.widget.Toast; @@ -39,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.system.AndroidExecutor; import org.briarproject.briar.R; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error; @@ -64,6 +69,7 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; @@ -75,6 +81,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; @@ -116,6 +123,7 @@ class AddNearbyContactViewModel extends AndroidViewModel private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private final EventBus eventBus; + private final AndroidExecutor androidExecutor; private final Executor ioExecutor; private final PluginManager pluginManager; private final PayloadEncoder payloadEncoder; @@ -124,28 +132,28 @@ class AddNearbyContactViewModel extends AndroidViewModel private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; - /** - * Set to true when the continue button is clicked, and false when the QR - * code fragment is shown. This prevents the QR code fragment from being - * shown automatically before the continue button has been clicked. - */ - private final MutableLiveData wasContinueClicked = - new MutableLiveData<>(false); - private final MutableLiveEvent showQrCodeFragment = + private final MutableLiveEvent checkPermissions = new MutableLiveEvent<>(); - private final MutableLiveEvent transportStateChanged = + private final MutableLiveEvent requestBluetoothDiscoverable = + new MutableLiveEvent<>(); + private final MutableLiveEvent showQrCodeFragment = new MutableLiveEvent<>(); private final MutableLiveData state = new MutableLiveData<>(); final QrCodeDecoder qrCodeDecoder; + final BroadcastReceiver bluetoothReceiver = new BluetoothStateReceiver(); @Nullable private final BluetoothAdapter bt; @Nullable private final Plugin wifiPlugin, bluetoothPlugin; + // UiThread - BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; + private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; + + private boolean wasContinueClicked = false; + private boolean isActivityResumed = false; /** * Records whether we've enabled the wifi plugin so we don't enable it more @@ -166,6 +174,7 @@ class AddNearbyContactViewModel extends AndroidViewModel @Inject AddNearbyContactViewModel(Application app, EventBus eventBus, + AndroidExecutor androidExecutor, @IoExecutor Executor ioExecutor, PluginManager pluginManager, PayloadEncoder payloadEncoder, @@ -175,6 +184,7 @@ class AddNearbyContactViewModel extends AndroidViewModel ConnectionManager connectionManager) { super(app); this.eventBus = eventBus; + this.androidExecutor = androidExecutor; this.ioExecutor = ioExecutor; this.pluginManager = pluginManager; this.payloadEncoder = payloadEncoder; @@ -185,13 +195,16 @@ class AddNearbyContactViewModel extends AndroidViewModel bt = BluetoothAdapter.getDefaultAdapter(); wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID); bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); - qrCodeDecoder = new QrCodeDecoder(ioExecutor, this); + qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this); eventBus.addListener(this); + IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); + getApplication().registerReceiver(bluetoothReceiver, filter); } @Override protected void onCleared() { super.onCleared(); + getApplication().unregisterReceiver(bluetoothReceiver); eventBus.removeListener(this); stopListening(); } @@ -201,7 +214,9 @@ class AddNearbyContactViewModel extends AndroidViewModel if (bluetoothDecision == REFUSED) { bluetoothDecision = UNKNOWN; // Ask again } - wasContinueClicked.setValue(true); + wasContinueClicked = true; + checkPermissions.setEvent(true); + showQrCodeFragmentIfAllowed(); } @UiThread @@ -266,7 +281,7 @@ class AddNearbyContactViewModel extends AndroidViewModel void startAddingContact() { // If we return to the intro fragment, the continue button needs to be // clicked again before showing the QR code fragment - wasContinueClicked.setValue(false); + wasContinueClicked = false; // If we return to the intro fragment, ask for Bluetooth // discoverability again before showing the QR code fragment bluetoothDecision = UNKNOWN; @@ -310,12 +325,12 @@ class AddNearbyContactViewModel extends AndroidViewModel if (LOG.isLoggable(INFO)) { LOG.info("Bluetooth state changed to " + t.getState()); } - transportStateChanged.setEvent(t.getTransportId()); + showQrCodeFragmentIfAllowed(); } else if (t.getTransportId().equals(LanTcpConstants.ID)) { if (LOG.isLoggable(INFO)) { LOG.info("Wifi state changed to " + t.getState()); } - transportStateChanged.setEvent(t.getTransportId()); + showQrCodeFragmentIfAllowed(); } } else if (e instanceof KeyAgreementListeningEvent) { LOG.info("KeyAgreementListeningEvent received"); @@ -344,6 +359,28 @@ class AddNearbyContactViewModel extends AndroidViewModel } } + @SuppressWarnings("StatementWithEmptyBody") + @UiThread + void showQrCodeFragmentIfAllowed() { + boolean permissionsGranted = areEssentialPermissionsGranted( + getApplication(), isBluetoothSupported()); + if (isActivityResumed && wasContinueClicked && permissionsGranted) { + if (isWifiReady() && isBluetoothReady()) { + LOG.info("Wifi and Bluetooth are ready"); + startAddingContact(); + } else { + enableWifiIfWeShould(); + if (bluetoothDecision == UNKNOWN) { + requestBluetoothDiscoverable.setEvent(true); + } else if (bluetoothDecision == REFUSED) { + // Ask again when the user clicks "continue" + } else { + enableBluetoothIfWeShould(); + } + } + } + } + /** * This sets the QR code by setting the state to KeyAgreementListening. */ @@ -383,8 +420,8 @@ class AddNearbyContactViewModel extends AndroidViewModel state.postValue(new AddContactState.Failed(e.isTooOld())); } catch (IOException | IllegalArgumentException e) { LOG.log(WARNING, "QR Code Invalid", e); - Toast.makeText(getApplication(), R.string.qr_code_invalid, - LENGTH_LONG).show(); + androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(), + R.string.qr_code_invalid, LENGTH_LONG).show()); resetPayloadFlags(); state.postValue(new AddContactState.Failed()); } @@ -423,6 +460,15 @@ class AddNearbyContactViewModel extends AndroidViewModel }); } + private class BluetoothStateReceiver extends BroadcastReceiver { + @UiThread + @Override + public void onReceive(Context context, Intent intent) { + LOG.info("Bluetooth scan mode changed"); + showQrCodeFragmentIfAllowed(); + } + } + private void tryToClose(DuplexTransportConnection conn) { try { conn.getReader().dispose(true, true); @@ -432,16 +478,33 @@ class AddNearbyContactViewModel extends AndroidViewModel } } - LiveData getWasContinueClicked() { - return wasContinueClicked; + /** + * Set to true in onPostResume() and false in onPause(). This prevents the + * QR code fragment from being shown if onRequestPermissionsResult() is + * called while the activity is paused, which could cause a crash due to + * https://issuetracker.google.com/issues/37067655. + * TODO check if this is still happening when using new permission requesting + */ + @UiThread + void setIsActivityResumed(boolean resumed) { + isActivityResumed = resumed; + // Workaround for + // https://code.google.com/p/android/issues/detail?id=190966 + showQrCodeFragmentIfAllowed(); } - /** - * Receives an event when the transport state of the WiFi or Bluetooth - * plugins changes. - */ - LiveEvent getTransportStateChanged() { - return transportStateChanged; + @UiThread + void setBluetoothDecision(BluetoothDecision decision) { + bluetoothDecision = decision; + showQrCodeFragmentIfAllowed(); + } + + LiveEvent getCheckPermissions() { + return checkPermissions; + } + + LiveEvent getRequestBluetoothDiscoverable() { + return requestBluetoothDiscoverable; } LiveEvent getShowQrCodeFragment() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java index ad61baa3c..bbb9dc875 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/QrCodeDecoder.java @@ -18,6 +18,7 @@ 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.bramble.api.system.AndroidExecutor; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -35,6 +36,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private static final Logger LOG = getLogger(QrCodeDecoder.class.getName()); + private final AndroidExecutor androidExecutor; private final Executor ioExecutor; private final Reader reader = new QRCodeReader(); private final ResultCallback callback; @@ -42,7 +44,9 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private Camera camera = null; private int cameraIndex = 0; - QrCodeDecoder(@IoExecutor Executor ioExecutor, ResultCallback callback) { + QrCodeDecoder(AndroidExecutor androidExecutor, + @IoExecutor Executor ioExecutor, ResultCallback callback) { + this.androidExecutor = androidExecutor; this.ioExecutor = ioExecutor; this.callback = callback; } @@ -104,9 +108,9 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { LOG.warning("Invalid preview frame"); } finally { reader.reset(); + androidExecutor.runOnUiThread(this::askForPreviewFrame); } }); - askForPreviewFrame(); } private static BinaryBitmap binarize(byte[] data, int width, int height, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/RequestBluetoothDiscoverable.java b/briar-android/src/main/java/org/briarproject/briar/android/util/RequestBluetoothDiscoverable.java new file mode 100644 index 000000000..8288aedac --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/RequestBluetoothDiscoverable.java @@ -0,0 +1,31 @@ +package org.briarproject.briar.android.util; + + +import android.content.Context; +import android.content.Intent; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import androidx.activity.result.contract.ActivityResultContract; +import androidx.annotation.Nullable; + +import static android.app.Activity.RESULT_CANCELED; +import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; +import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION; + +@NotNullByDefault +public class RequestBluetoothDiscoverable + extends ActivityResultContract { + + @Override + public Intent createIntent(Context context, Integer duration) { + Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); + i.putExtra(EXTRA_DISCOVERABLE_DURATION, duration); + return i; + } + + @Override + public Boolean parseResult(int resultCode, @Nullable Intent intent) { + return resultCode != RESULT_CANCELED; + } +} diff --git a/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml b/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml index c9867a627..bab5a0b80 100644 --- a/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml +++ b/briar-android/src/main/res/layout/activity_fragment_container_toolbar.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".android.contact.add.nearby.KeyAgreementActivity"> + tools:context=".android.contact.add.nearby.AddNearbyContactActivity"> From a37af592cdb05de1af483518719f8d9cf0a69813 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 24 Mar 2021 14:48:45 -0300 Subject: [PATCH 10/21] Use new ActivityResultLauncher to request permissions for AddNearbyContact --- .../briar/android/activity/RequestCodes.java | 1 - .../add/nearby/AddNearbyContactActivity.java | 18 ++++----- .../AddNearbyContactPermissionManager.java | 38 ++++++++++--------- .../add/nearby/AddNearbyContactViewModel.java | 6 ++- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index de989877f..c6bf3b520 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -9,7 +9,6 @@ public interface RequestCodes { int REQUEST_WRITE_BLOG_POST = 5; int REQUEST_SHARE_BLOG = 6; int REQUEST_RINGTONE = 7; - int REQUEST_PERMISSION_CAMERA_LOCATION = 8; int REQUEST_DOZE_WHITELISTING = 9; int REQUEST_UNLOCK = 11; int REQUEST_KEYGUARD_UNLOCK = 12; 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 126590ae1..1fdbcd550 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 @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; @@ -51,6 +52,10 @@ public class AddNearbyContactActivity extends BriarActivity private AddNearbyContactViewModel viewModel; private AddNearbyContactPermissionManager permissionManager; + private final ActivityResultLauncher permissionLauncher = + registerForActivityResult(new RequestMultiplePermissions(), r -> + permissionManager.onRequestPermissionResult(r, + viewModel::showQrCodeFragmentIfAllowed)); private final ActivityResultLauncher bluetoothLauncher = registerForActivityResult(new RequestBluetoothDiscoverable(), this::onBluetoothDiscoverableResult); @@ -61,7 +66,7 @@ public class AddNearbyContactActivity extends BriarActivity viewModel = new ViewModelProvider(this, viewModelFactory) .get(AddNearbyContactViewModel.class); permissionManager = new AddNearbyContactPermissionManager(this, - viewModel.isBluetoothSupported()); + permissionLauncher::launch, viewModel.isBluetoothSupported()); } @Override @@ -75,7 +80,7 @@ public class AddNearbyContactActivity extends BriarActivity showInitialFragment(AddNearbyContactIntroFragment.newInstance()); } viewModel.getCheckPermissions().observeEvent(this, check -> - permissionManager.checkPermissions()); // never false + permissionManager.checkPermissions()); viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r -> requestBluetoothDiscoverable()); // never false viewModel.getShowQrCodeFragment().observeEvent(this, show -> { @@ -125,15 +130,6 @@ public class AddNearbyContactActivity extends BriarActivity return super.onOptionsItemSelected(item); } - @Override - public void onRequestPermissionsResult(int requestCode, - String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, - grantResults); - permissionManager.onRequestPermissionsResult(requestCode, permissions, - grantResults, viewModel::showQrCodeFragmentIfAllowed); - } - @Override public void onBackPressed() { if (viewModel.getState().getValue() instanceof Failed) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java index 937799e10..426da7cdc 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -5,9 +5,11 @@ import android.content.Context; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.BaseActivity; +import java.util.Map; + import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; -import androidx.core.app.ActivityCompat; +import androidx.core.util.Consumer; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.CAMERA; @@ -15,7 +17,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; import static androidx.core.content.ContextCompat.checkSelfPermission; -import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; class AddNearbyContactPermissionManager { @@ -28,11 +29,14 @@ class AddNearbyContactPermissionManager { private Permission locationPermission = Permission.UNKNOWN; private final BaseActivity ctx; + private final Consumer requestPermissions; private final boolean isBluetoothSupported; AddNearbyContactPermissionManager(BaseActivity ctx, + Consumer requestPermissions, boolean isBluetoothSupported) { this.ctx = ctx; + this.requestPermissions = requestPermissions; this.isBluetoothSupported = isBluetoothSupported; } @@ -116,15 +120,12 @@ class AddNearbyContactPermissionManager { } else { permissions = new String[] {CAMERA}; } - ActivityCompat.requestPermissions(ctx, permissions, - REQUEST_PERMISSION_CAMERA_LOCATION); + requestPermissions.accept(permissions); } - void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults, Runnable onPermissionsGranted) { - if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION) - throw new AssertionError(); - if (gotPermission(CAMERA, permissions, grantResults)) { + void onRequestPermissionResult(Map result, + Runnable onPermissionsGranted) { + if (gotPermission(CAMERA, result)) { cameraPermission = Permission.GRANTED; } else if (shouldShowRationale(CAMERA)) { cameraPermission = Permission.SHOW_RATIONALE; @@ -132,8 +133,7 @@ class AddNearbyContactPermissionManager { cameraPermission = Permission.PERMANENTLY_DENIED; } if (isBluetoothSupported) { - if (gotPermission(ACCESS_FINE_LOCATION, permissions, - grantResults)) { + if (gotPermission(ACCESS_FINE_LOCATION, result)) { locationPermission = Permission.GRANTED; } else if (shouldShowRationale(ACCESS_FINE_LOCATION)) { locationPermission = Permission.SHOW_RATIONALE; @@ -150,13 +150,15 @@ class AddNearbyContactPermissionManager { if (checkPermissions()) onPermissionsGranted.run(); } - private boolean gotPermission(String permission, String[] permissions, - int[] grantResults) { - for (int i = 0; i < permissions.length; i++) { - if (permission.equals(permissions[i])) - return grantResults[i] == PERMISSION_GRANTED; - } - return false; + private boolean gotPermission(String permission, + Map result) { + Boolean permissionResult = result.get(permission); + return permissionResult == null ? + isGranted(permission) : permissionResult; + } + + private boolean isGranted(String permission) { + return checkSelfPermission(ctx, permission) == PERMISSION_GRANTED; } private boolean shouldShowRationale(String permission) { 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 46182ce29..40650d189 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 @@ -70,6 +70,7 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; @@ -464,7 +465,8 @@ class AddNearbyContactViewModel extends AndroidViewModel @UiThread @Override public void onReceive(Context context, Intent intent) { - LOG.info("Bluetooth scan mode changed"); + int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, -1); + LOG.info("Bluetooth scan mode changed: " + scanMode); showQrCodeFragmentIfAllowed(); } } @@ -483,7 +485,7 @@ class AddNearbyContactViewModel extends AndroidViewModel * QR code fragment from being shown if onRequestPermissionsResult() is * called while the activity is paused, which could cause a crash due to * https://issuetracker.google.com/issues/37067655. - * TODO check if this is still happening when using new permission requesting + * TODO check if this is still happening with new permission requesting */ @UiThread void setIsActivityResumed(boolean resumed) { From 1d44305e34b48cf29c2cda5934994d3284794280 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 24 Mar 2021 15:23:16 -0300 Subject: [PATCH 11/21] Catch exception when calling Camera#getParameters() Fixes #1982 --- .../briar/android/contact/add/nearby/CameraView.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java index 1afa75ec3..7a5720459 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/CameraView.java @@ -127,7 +127,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback, setDisplayOrientation(getScreenRotationDegrees()); if (camera == null) throw new CameraException("No camera found"); // Use barcode scene mode if it's available - Parameters params = camera.getParameters(); + Parameters params; + try { + params = camera.getParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } params = setSceneMode(camera, params); if (SCENE_MODE_BARCODE.equals(params.getSceneMode())) { // If the scene mode enabled the flash, try to disable it From 4f3e4b019a061c3d12b7306e18f5f18f150f532c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 29 Mar 2021 11:30:17 -0300 Subject: [PATCH 12/21] Request user to turn on location for adding contact nearby on API 28+ --- .../AddNearbyContactPermissionManager.java | 37 ++++++++++++++++++- .../add/nearby/AddNearbyContactViewModel.java | 5 ++- briar-android/src/main/res/values/strings.xml | 3 ++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java index 426da7cdc..168087611 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -1,6 +1,8 @@ package org.briarproject.briar.android.contact.add.nearby; import android.content.Context; +import android.content.Intent; +import android.location.LocationManager; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.BaseActivity; @@ -15,6 +17,7 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.CAMERA; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; +import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS; import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; import static androidx.core.content.ContextCompat.checkSelfPermission; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; @@ -45,6 +48,19 @@ class AddNearbyContactPermissionManager { locationPermission = Permission.UNKNOWN; } + /** + * @return true if location is enabled, + * or it isn't required due to this being a SDK < 28 device. + */ + static boolean isLocationEnabled(Context ctx) { + if (SDK_INT >= 28) { + LocationManager lm = ctx.getSystemService(LocationManager.class); + return lm.isLocationEnabled(); + } else { + return true; + } + } + static boolean areEssentialPermissionsGranted(Context ctx, boolean isBluetoothSupported) { int ok = PERMISSION_GRANTED; @@ -61,7 +77,8 @@ class AddNearbyContactPermissionManager { } boolean checkPermissions() { - if (areEssentialPermissionsGranted()) return true; + boolean locationEnabled = isLocationEnabled(ctx); + if (locationEnabled && areEssentialPermissionsGranted()) return true; // If an essential permission has been permanently denied, ask the // user to change the setting if (cameraPermission == Permission.PERMANENTLY_DENIED) { @@ -86,8 +103,10 @@ class AddNearbyContactPermissionManager { } else if (locationPermission == Permission.SHOW_RATIONALE) { showRationale(R.string.permission_location_title, R.string.permission_location_request_body); - } else { + } else if (isLocationEnabled(ctx)) { requestPermissions(); + } else { + showLocationDialog(ctx); } return false; } @@ -113,6 +132,20 @@ class AddNearbyContactPermissionManager { builder.show(); } + private static void showLocationDialog(Context ctx) { + AlertDialog.Builder builder = + new AlertDialog.Builder(ctx, R.style.BriarDialogTheme); + builder.setTitle(R.string.permission_location_setting_title); + builder.setMessage(R.string.permission_location_setting_body); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.permission_location_setting_button, + (dialog, which) -> { + Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS); + ctx.startActivity(i); + }); + builder.show(); + } + private void requestPermissions() { String[] permissions; if (isBluetoothSupported) { 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 40650d189..5683b0df5 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 @@ -83,6 +83,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.isLocationEnabled; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; @@ -365,7 +366,9 @@ class AddNearbyContactViewModel extends AndroidViewModel void showQrCodeFragmentIfAllowed() { boolean permissionsGranted = areEssentialPermissionsGranted( getApplication(), isBluetoothSupported()); - if (isActivityResumed && wasContinueClicked && permissionsGranted) { + boolean locationEnabled = isLocationEnabled(getApplication()); + if (isActivityResumed && wasContinueClicked && permissionsGranted && + locationEnabled) { if (isWifiReady() && isBluetoothReady()) { LOG.info("Wifi and Bluetooth are ready"); startAddingContact(); diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index faa7ae0d9..00e0ed789 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -609,6 +609,9 @@ To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone. You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access. You have denied access to your location, but Briar needs this permission to discover Bluetooth devices.\n\nPlease consider granting access. + Location setting + Your device\'s location setting must be turned on to find other devices via Bluetooth. Please enable location to continue. You can disable it again afterwards. + Enable location QR code Show QR code fullscreen From 5b52417d20e81d2fccc7f1bf086d3ae5ad2b86ea Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 1 Apr 2021 15:34:21 -0300 Subject: [PATCH 13/21] Check if Bluetooth is supported before requesting discoverability --- .../add/nearby/AddNearbyContactActivity.java | 16 ++++++---------- .../add/nearby/AddNearbyContactViewModel.java | 7 ++++++- 2 files changed, 12 insertions(+), 11 deletions(-) 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 1fdbcd550..6b731812b 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 @@ -143,17 +143,13 @@ public class AddNearbyContactActivity extends BriarActivity } private void requestBluetoothDiscoverable() { - if (!viewModel.isBluetoothSupported()) { - viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER); + Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); + if (i.resolveActivity(getPackageManager()) != null) { + LOG.info("Asking for Bluetooth discoverability"); + viewModel.setBluetoothDecision(BluetoothDecision.WAITING); + bluetoothLauncher.launch(120); // 2min discoverable } else { - Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); - if (i.resolveActivity(getPackageManager()) != null) { - LOG.info("Asking for Bluetooth discoverability"); - viewModel.setBluetoothDecision(BluetoothDecision.WAITING); - bluetoothLauncher.launch(120); // 2min discoverable - } else { - viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER); - } + viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER); } } 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 5683b0df5..5dc2da575 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 @@ -84,6 +84,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.isLocationEnabled; +import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; @@ -375,7 +376,11 @@ class AddNearbyContactViewModel extends AndroidViewModel } else { enableWifiIfWeShould(); if (bluetoothDecision == UNKNOWN) { - requestBluetoothDiscoverable.setEvent(true); + if (isBluetoothSupported()) { + requestBluetoothDiscoverable.setEvent(true); + } else { + bluetoothDecision = NO_ADAPTER; + } } else if (bluetoothDecision == REFUSED) { // Ask again when the user clicks "continue" } else { From 0ee4ade4047677f599411ae62709bf4ab6948e3b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 7 Apr 2021 16:18:18 -0300 Subject: [PATCH 14/21] One more round of addressing AddNearbyContact review feedback --- .../add/nearby/AddNearbyContactActivity.java | 20 +---------- .../add/nearby/AddNearbyContactFragment.java | 4 +-- .../nearby/AddNearbyContactIntroFragment.java | 20 +++++++++-- .../AddNearbyContactPermissionManager.java | 18 +++++++--- .../add/nearby/AddNearbyContactViewModel.java | 34 +++++++++++-------- 5 files changed, 53 insertions(+), 43 deletions(-) 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 6b731812b..1cb3f8783 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 @@ -25,7 +25,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; @@ -50,12 +49,6 @@ public class AddNearbyContactActivity extends BriarActivity ViewModelProvider.Factory viewModelFactory; private AddNearbyContactViewModel viewModel; - private AddNearbyContactPermissionManager permissionManager; - - private final ActivityResultLauncher permissionLauncher = - registerForActivityResult(new RequestMultiplePermissions(), r -> - permissionManager.onRequestPermissionResult(r, - viewModel::showQrCodeFragmentIfAllowed)); private final ActivityResultLauncher bluetoothLauncher = registerForActivityResult(new RequestBluetoothDiscoverable(), this::onBluetoothDiscoverableResult); @@ -65,8 +58,6 @@ public class AddNearbyContactActivity extends BriarActivity component.inject(this); viewModel = new ViewModelProvider(this, viewModelFactory) .get(AddNearbyContactViewModel.class); - permissionManager = new AddNearbyContactPermissionManager(this, - permissionLauncher::launch, viewModel.isBluetoothSupported()); } @Override @@ -79,8 +70,6 @@ public class AddNearbyContactActivity extends BriarActivity if (state == null) { showInitialFragment(AddNearbyContactIntroFragment.newInstance()); } - viewModel.getCheckPermissions().observeEvent(this, check -> - permissionManager.checkPermissions()); viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r -> requestBluetoothDiscoverable()); // never false viewModel.getShowQrCodeFragment().observeEvent(this, show -> { @@ -92,13 +81,6 @@ public class AddNearbyContactActivity extends BriarActivity .observe(this, this::onAddContactStateChanged); } - @Override - public void onStart() { - super.onStart(); - // Permissions may have been granted manually while we were stopped - permissionManager.resetPermissions(); - } - @Override protected void onPostResume() { super.onPostResume(); @@ -165,7 +147,7 @@ public class AddNearbyContactActivity extends BriarActivity } } - private void onAddContactStateChanged(AddContactState state) { + private void onAddContactStateChanged(@Nullable AddContactState state) { if (state instanceof ContactExchangeFinished) { ContactExchangeResult result = ((ContactExchangeFinished) state).result; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java index 6f0a841cb..cd1a23f40 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java @@ -100,7 +100,7 @@ public class AddNearbyContactFragment extends BaseFragment public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); - cameraView.setPreviewConsumer(viewModel.qrCodeDecoder); + cameraView.setPreviewConsumer(viewModel.getQrCodeDecoder()); } @Override @@ -153,7 +153,7 @@ public class AddNearbyContactFragment extends BaseFragment } @UiThread - private void onAddContactStateChanged(AddContactState state) { + private void onAddContactStateChanged(@Nullable AddContactState state) { if (state instanceof AddContactState.KeyAgreementListening) { Bitmap qrCode = ((AddContactState.KeyAgreementListening) state).qrCode; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java index 7bc666550..5a9aa3864 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java @@ -15,6 +15,8 @@ import org.briarproject.briar.android.fragment.BaseFragment; import javax.annotation.Nullable; import javax.inject.Inject; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.lifecycle.ViewModelProvider; import static android.view.View.FOCUS_DOWN; @@ -23,15 +25,22 @@ import static android.view.View.FOCUS_DOWN; @ParametersNotNullByDefault public class AddNearbyContactIntroFragment extends BaseFragment { - public static final String TAG = AddNearbyContactIntroFragment.class.getName(); + public static final String TAG = + AddNearbyContactIntroFragment.class.getName(); @Inject ViewModelProvider.Factory viewModelFactory; private AddNearbyContactViewModel viewModel; + private AddNearbyContactPermissionManager permissionManager; private ScrollView scrollView; + private final ActivityResultLauncher permissionLauncher = + registerForActivityResult(new RequestMultiplePermissions(), r -> + permissionManager.onRequestPermissionResult(r, + viewModel::showQrCodeFragmentIfAllowed)); + public static AddNearbyContactIntroFragment newInstance() { Bundle args = new Bundle(); AddNearbyContactIntroFragment @@ -45,6 +54,9 @@ public class AddNearbyContactIntroFragment extends BaseFragment { component.inject(this); viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) .get(AddNearbyContactViewModel.class); + permissionManager = new AddNearbyContactPermissionManager( + requireActivity(), permissionLauncher::launch, + viewModel.isBluetoothSupported()); } @Nullable @@ -57,13 +69,17 @@ public class AddNearbyContactIntroFragment extends BaseFragment { false); scrollView = v.findViewById(R.id.scrollView); View button = v.findViewById(R.id.continueButton); - button.setOnClickListener(view -> viewModel.onContinueClicked()); + button.setOnClickListener(view -> viewModel.onContinueClicked(() -> + permissionManager.checkPermissions() + )); return v; } @Override public void onStart() { super.onStart(); + // Permissions may have been granted manually while we were stopped + permissionManager.resetPermissions(); scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java index 168087611..d2d387b5f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -1,23 +1,26 @@ package org.briarproject.briar.android.contact.add.nearby; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.location.LocationManager; +import android.widget.Toast; import org.briarproject.briar.R; -import org.briarproject.briar.android.activity.BaseActivity; import java.util.Map; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.core.util.Consumer; +import androidx.fragment.app.FragmentActivity; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.CAMERA; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS; +import static android.widget.Toast.LENGTH_LONG; import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; import static androidx.core.content.ContextCompat.checkSelfPermission; import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; @@ -31,11 +34,11 @@ class AddNearbyContactPermissionManager { private Permission cameraPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN; - private final BaseActivity ctx; + private final FragmentActivity ctx; private final Consumer requestPermissions; private final boolean isBluetoothSupported; - AddNearbyContactPermissionManager(BaseActivity ctx, + AddNearbyContactPermissionManager(FragmentActivity ctx, Consumer requestPermissions, boolean isBluetoothSupported) { this.ctx = ctx; @@ -70,7 +73,7 @@ class AddNearbyContactPermissionManager { !isBluetoothSupported); } - boolean areEssentialPermissionsGranted() { + private boolean areEssentialPermissionsGranted() { return cameraPermission == Permission.GRANTED && (SDK_INT < 23 || locationPermission == Permission.GRANTED || !isBluetoothSupported); @@ -141,7 +144,12 @@ class AddNearbyContactPermissionManager { builder.setPositiveButton(R.string.permission_location_setting_button, (dialog, which) -> { Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS); - ctx.startActivity(i); + try { + ctx.startActivity(i); + } catch (ActivityNotFoundException e) { + Toast.makeText(ctx, R.string.error_start_activity, + LENGTH_LONG).show(); + } }); builder.show(); } 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 5dc2da575..eca13d40d 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 @@ -65,6 +65,7 @@ import javax.inject.Provider; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.core.util.Supplier; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -135,8 +136,6 @@ class AddNearbyContactViewModel extends AndroidViewModel private final ContactExchangeManager contactExchangeManager; private final ConnectionManager connectionManager; - private final MutableLiveEvent checkPermissions = - new MutableLiveEvent<>(); private final MutableLiveEvent requestBluetoothDiscoverable = new MutableLiveEvent<>(); private final MutableLiveEvent showQrCodeFragment = @@ -144,8 +143,9 @@ class AddNearbyContactViewModel extends AndroidViewModel private final MutableLiveData state = new MutableLiveData<>(); - final QrCodeDecoder qrCodeDecoder; - final BroadcastReceiver bluetoothReceiver = new BluetoothStateReceiver(); + private final QrCodeDecoder qrCodeDecoder; + private final BroadcastReceiver bluetoothReceiver = + new BluetoothStateReceiver(); @Nullable private final BluetoothAdapter bt; @@ -171,7 +171,7 @@ class AddNearbyContactViewModel extends AndroidViewModel private boolean hasEnabledBluetooth = false; @Nullable - private KeyAgreementTask task; + private volatile KeyAgreementTask task; private volatile boolean gotLocalPayload = false, gotRemotePayload = false; @Inject @@ -213,13 +213,12 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - void onContinueClicked() { + void onContinueClicked(Supplier checkPermissions) { if (bluetoothDecision == REFUSED) { bluetoothDecision = UNKNOWN; // Ask again } wasContinueClicked = true; - checkPermissions.setEvent(true); - showQrCodeFragmentIfAllowed(); + if (checkPermissions.get()) showQrCodeFragmentIfAllowed(); } @UiThread @@ -228,7 +227,7 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - boolean isWifiReady() { + private boolean isWifiReady() { if (wifiPlugin == null) return true; // Continue without wifi State state = wifiPlugin.getState(); // Wait for plugin to become enabled @@ -236,7 +235,7 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - boolean isBluetoothReady() { + private boolean isBluetoothReady() { if (bt == null || bluetoothPlugin == null) { // Continue without Bluetooth return true; @@ -256,7 +255,7 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - void enableWifiIfWeShould() { + private void enableWifiIfWeShould() { if (hasEnabledWifi) return; if (wifiPlugin == null) return; State state = wifiPlugin.getState(); @@ -268,7 +267,7 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - void enableBluetoothIfWeShould() { + private void enableBluetoothIfWeShould() { if (bluetoothDecision != BluetoothDecision.ACCEPTED) return; if (hasEnabledBluetooth) return; if (bluetoothPlugin == null || !isBluetoothSupported()) return; @@ -281,7 +280,7 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - void startAddingContact() { + private void startAddingContact() { // If we return to the intro fragment, the continue button needs to be // clicked again before showing the QR code fragment wasContinueClicked = false; @@ -292,6 +291,8 @@ class AddNearbyContactViewModel extends AndroidViewModel // Bluetooth again hasEnabledWifi = false; hasEnabledBluetooth = false; + // reset state, so we don't show an old QR code again + state.setValue(null); // start to listen with a KeyAgreementTask startListening(); showQrCodeFragment.setEvent(true); @@ -509,8 +510,8 @@ class AddNearbyContactViewModel extends AndroidViewModel showQrCodeFragmentIfAllowed(); } - LiveEvent getCheckPermissions() { - return checkPermissions; + QrCodeDecoder getQrCodeDecoder() { + return qrCodeDecoder; } LiveEvent getRequestBluetoothDiscoverable() { @@ -521,6 +522,9 @@ class AddNearbyContactViewModel extends AndroidViewModel return showQrCodeFragment; } + /** + * This LiveData will be null initially. + */ LiveData getState() { return state; } From fe1c6acebb77493592b768dfe66a7817c564f3e1 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 8 Apr 2021 17:03:28 +0100 Subject: [PATCH 15/21] Remove workaround for Android issue #190966. --- .../add/nearby/AddNearbyContactActivity.java | 12 ------------ .../AddNearbyContactPermissionManager.java | 6 ------ .../add/nearby/AddNearbyContactViewModel.java | 19 +------------------ 3 files changed, 1 insertion(+), 36 deletions(-) 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 1cb3f8783..fcc80cef9 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 @@ -81,18 +81,6 @@ public class AddNearbyContactActivity extends BriarActivity .observe(this, this::onAddContactStateChanged); } - @Override - protected void onPostResume() { - super.onPostResume(); - viewModel.setIsActivityResumed(true); - } - - @Override - protected void onPause() { - super.onPause(); - viewModel.setIsActivityResumed(false); - } - private void onBluetoothDiscoverableResult(boolean discoverable) { if (discoverable) { LOG.info("Bluetooth discoverability was accepted"); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java index d2d387b5f..f04102c1e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -182,12 +182,6 @@ class AddNearbyContactPermissionManager { locationPermission = Permission.PERMANENTLY_DENIED; } } - // If a permission dialog has been shown, showing the QR code fragment - // on this call path would cause a crash due to - // https://code.google.com/p/android/issues/detail?id=190966. - // In that case the isResumed flag prevents the fragment from being - // shown here, and showQrCodeFragmentIfAllowed() will be called again - // from onPostResume(). if (checkPermissions()) onPermissionsGranted.run(); } 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 eca13d40d..33e44182e 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 @@ -156,7 +156,6 @@ class AddNearbyContactViewModel extends AndroidViewModel private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN; private boolean wasContinueClicked = false; - private boolean isActivityResumed = false; /** * Records whether we've enabled the wifi plugin so we don't enable it more @@ -369,8 +368,7 @@ class AddNearbyContactViewModel extends AndroidViewModel boolean permissionsGranted = areEssentialPermissionsGranted( getApplication(), isBluetoothSupported()); boolean locationEnabled = isLocationEnabled(getApplication()); - if (isActivityResumed && wasContinueClicked && permissionsGranted && - locationEnabled) { + if (wasContinueClicked && permissionsGranted && locationEnabled) { if (isWifiReady() && isBluetoothReady()) { LOG.info("Wifi and Bluetooth are ready"); startAddingContact(); @@ -489,21 +487,6 @@ class AddNearbyContactViewModel extends AndroidViewModel } } - /** - * Set to true in onPostResume() and false in onPause(). This prevents the - * QR code fragment from being shown if onRequestPermissionsResult() is - * called while the activity is paused, which could cause a crash due to - * https://issuetracker.google.com/issues/37067655. - * TODO check if this is still happening with new permission requesting - */ - @UiThread - void setIsActivityResumed(boolean resumed) { - isActivityResumed = resumed; - // Workaround for - // https://code.google.com/p/android/issues/detail?id=190966 - showQrCodeFragmentIfAllowed(); - } - @UiThread void setBluetoothDecision(BluetoothDecision decision) { bluetoothDecision = decision; From 212751c83541ab62076e40d28e46919abd8925f9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 8 Apr 2021 17:11:07 +0100 Subject: [PATCH 16/21] Return a value instead of passing a runnable argument. --- .../add/nearby/AddNearbyContactIntroFragment.java | 9 ++++++--- .../add/nearby/AddNearbyContactPermissionManager.java | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java index 5a9aa3864..08b61094a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java @@ -37,9 +37,12 @@ public class AddNearbyContactIntroFragment extends BaseFragment { private ScrollView scrollView; private final ActivityResultLauncher permissionLauncher = - registerForActivityResult(new RequestMultiplePermissions(), r -> - permissionManager.onRequestPermissionResult(r, - viewModel::showQrCodeFragmentIfAllowed)); + registerForActivityResult(new RequestMultiplePermissions(), r -> { + permissionManager.onRequestPermissionResult(r); + if (permissionManager.checkPermissions()) { + viewModel.showQrCodeFragmentIfAllowed(); + } + }); public static AddNearbyContactIntroFragment newInstance() { Bundle args = new Bundle(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java index f04102c1e..b0bc75fbe 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java @@ -164,8 +164,7 @@ class AddNearbyContactPermissionManager { requestPermissions.accept(permissions); } - void onRequestPermissionResult(Map result, - Runnable onPermissionsGranted) { + void onRequestPermissionResult(Map result) { if (gotPermission(CAMERA, result)) { cameraPermission = Permission.GRANTED; } else if (shouldShowRationale(CAMERA)) { @@ -182,7 +181,6 @@ class AddNearbyContactPermissionManager { locationPermission = Permission.PERMANENTLY_DENIED; } } - if (checkPermissions()) onPermissionsGranted.run(); } private boolean gotPermission(String permission, From c53fc5eaa36e978d64532af55e6f893276eb5dc1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 8 Apr 2021 13:55:26 -0300 Subject: [PATCH 17/21] Add inspection profile to repo so we can all share the same inspection profile and benefit from its warnings --- .gitignore | 1 + .idea/inspectionProfiles/Project_Default.xml | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.gitignore b/.gitignore index 21c3404ab..f4f05ec62 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ local.properties # Android Studio .idea/* +!.idea/inspectionProfiles/ !.idea/runConfigurations/ !.idea/codeStyleSettings.xml !.idea/codeStyles diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..9180c3471 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file From a64878bd00ee0e87fcf67da2a0419b58452d1a18 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 8 Apr 2021 14:21:17 -0300 Subject: [PATCH 18/21] Also reset payload flags when resetting AddNearbyContact state --- .../android/contact/add/nearby/AddNearbyContactViewModel.java | 1 + 1 file changed, 1 insertion(+) 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 33e44182e..769b5f49b 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 @@ -292,6 +292,7 @@ class AddNearbyContactViewModel extends AndroidViewModel hasEnabledBluetooth = false; // reset state, so we don't show an old QR code again state.setValue(null); + resetPayloadFlags(); // start to listen with a KeyAgreementTask startListening(); showQrCodeFragment.setEvent(true); From 5e84e5b8b6e590e829ea61c1c60afbf340dfc1f1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 9 Apr 2021 15:20:39 -0300 Subject: [PATCH 19/21] Stop listening to key agreement connections when leaving fragment Also don't liberate screen orientation when backing out of adding a contact nearby --- .../add/nearby/AddNearbyContactActivity.java | 3 ++- .../nearby/AddNearbyContactErrorFragment.java | 19 +++++++++++++++++++ .../add/nearby/AddNearbyContactFragment.java | 18 +++--------------- .../nearby/AddNearbyContactIntroFragment.java | 9 +++++++++ .../add/nearby/AddNearbyContactViewModel.java | 7 +++++-- 5 files changed, 38 insertions(+), 18 deletions(-) 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 fcc80cef9..6dd6ca690 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 @@ -103,7 +103,8 @@ public class AddNearbyContactActivity extends BriarActivity @Override public void onBackPressed() { if (viewModel.getState().getValue() instanceof Failed) { - // re-create this activity when going back in failed state + // Re-create this activity when going back in failed state. + // This will also re-create the ViewModel, so we start fresh. Intent i = new Intent(this, AddNearbyContactActivity.class); i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); 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 15dc04848..9944f3663 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 @@ -15,8 +15,11 @@ import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.util.UiUtils; +import javax.inject.Inject; + import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.view.View.GONE; @@ -30,6 +33,11 @@ public class AddNearbyContactErrorFragment extends BaseFragment { AddNearbyContactErrorFragment.class.getName(); private static final String ERROR_MSG = "errorMessage"; + @Inject + ViewModelProvider.Factory viewModelFactory; + + private AddNearbyContactViewModel viewModel; + public static AddNearbyContactErrorFragment newInstance(String errorMsg) { AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment(); Bundle args = new Bundle(); @@ -46,6 +54,8 @@ public class AddNearbyContactErrorFragment extends BaseFragment { @Override public void injectFragment(ActivityComponent component) { component.inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(AddNearbyContactViewModel.class); } @Nullable @@ -81,6 +91,15 @@ public class AddNearbyContactErrorFragment extends BaseFragment { return v; } + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // We don't do this in AddNearbyContactFragment#onDestroy() + // because it gets called when creating AddNearbyContactFragment + // in landscape orientation to force portrait orientation. + viewModel.stopListening(); + } + private void triggerFeedback() { UiUtils.triggerFeedback(requireContext()); finish(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java index cd1a23f40..fc3d43feb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java @@ -31,7 +31,6 @@ import androidx.annotation.UiThread; import androidx.lifecycle.ViewModelProvider; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; @@ -86,23 +85,19 @@ public class AddNearbyContactFragment extends BaseFragment public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); cameraView = view.findViewById(R.id.camera_view); + cameraView.setPreviewConsumer(viewModel.getQrCodeDecoder()); cameraOverlay = view.findViewById(R.id.camera_overlay); statusView = view.findViewById(R.id.status_container); status = view.findViewById(R.id.connect_status); qrCodeView = view.findViewById(R.id.qr_code_view); qrCodeView.setFullscreenListener(this); + requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); + viewModel.getState().observe(getViewLifecycleOwner(), this::onAddContactStateChanged); } - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); - cameraView.setPreviewConsumer(viewModel.getQrCodeDecoder()); - } - @Override public void onStart() { super.onStart(); @@ -123,13 +118,6 @@ public class AddNearbyContactFragment extends BaseFragment } } - @Override - public void onDestroy() { - requireActivity() - .setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); - super.onDestroy(); - } - @Override public void setFullscreen(boolean fullscreen) { LinearLayout.LayoutParams statusParams, qrCodeParams; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java index 08b61094a..eb5f3aede 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java @@ -78,6 +78,15 @@ public class AddNearbyContactIntroFragment extends BaseFragment { return v; } + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // We don't do this in AddNearbyContactFragment#onDestroy() + // because it gets called when creating AddNearbyContactFragment + // in landscape orientation to force portrait orientation. + viewModel.stopListening(); + } + @Override public void onStart() { super.onStart(); 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 769b5f49b..0f7c0e5bb 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 @@ -314,10 +314,13 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - private void stopListening() { + void stopListening() { KeyAgreementTask oldTask = task; ioExecutor.execute(() -> { - if (oldTask != null) oldTask.stopListening(); + if (oldTask != null) { + oldTask.stopListening(); + task = null; + } }); } From 6ee57315dd05652935cedcc23f56382b504b3bb6 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 12 Apr 2021 08:18:54 -0300 Subject: [PATCH 20/21] Prevent NPE when onQrCodeDecoded() is called after we stop to listen --- .../contact/add/nearby/AddNearbyContactViewModel.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 0f7c0e5bb..7d69bd954 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 @@ -74,7 +74,6 @@ import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.widget.Toast.LENGTH_LONG; -import static java.util.Objects.requireNonNull; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; @@ -417,15 +416,16 @@ class AddNearbyContactViewModel extends AndroidViewModel @IoExecutor public void onQrCodeDecoded(Result result) { LOG.info("Got result from decoder"); + KeyAgreementTask currentTask = task; // Ignore results until the KeyAgreementTask is ready - if (!gotLocalPayload || gotRemotePayload) return; + if (!gotLocalPayload || gotRemotePayload || currentTask == null) return; try { byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); if (LOG.isLoggable(INFO)) LOG.info("Remote payload is " + payloadBytes.length + " bytes"); Payload remotePayload = payloadParser.parse(payloadBytes); gotRemotePayload = true; - requireNonNull(task).connectAndRunProtocol(remotePayload); + currentTask.connectAndRunProtocol(remotePayload); state.postValue(new AddContactState.QrCodeScanned()); } catch (UnsupportedVersionException e) { resetPayloadFlags(); From cdae8b35f5c47e826cdd75646d311b84c5e1fabf Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 9 Apr 2021 11:09:54 +0100 Subject: [PATCH 21/21] Another small refactoring to make control flow easier to understand. --- .../add/nearby/AddNearbyContactIntroFragment.java | 9 ++++++--- .../contact/add/nearby/AddNearbyContactViewModel.java | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java index eb5f3aede..4e3e0c1d7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactIntroFragment.java @@ -72,9 +72,12 @@ public class AddNearbyContactIntroFragment extends BaseFragment { false); scrollView = v.findViewById(R.id.scrollView); View button = v.findViewById(R.id.continueButton); - button.setOnClickListener(view -> viewModel.onContinueClicked(() -> - permissionManager.checkPermissions() - )); + button.setOnClickListener(view -> { + viewModel.onContinueClicked(); + if (permissionManager.checkPermissions()) { + viewModel.showQrCodeFragmentIfAllowed(); + } + }); return v; } 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 7d69bd954..d79ef6b1b 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 @@ -65,7 +65,6 @@ import javax.inject.Provider; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import androidx.core.util.Supplier; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -211,12 +210,11 @@ class AddNearbyContactViewModel extends AndroidViewModel } @UiThread - void onContinueClicked(Supplier checkPermissions) { + void onContinueClicked() { if (bluetoothDecision == REFUSED) { bluetoothDecision = UNKNOWN; // Ask again } wasContinueClicked = true; - if (checkPermissions.get()) showQrCodeFragmentIfAllowed(); } @UiThread