diff --git a/bramble-android/build.gradle b/bramble-android/build.gradle
index c3e545caa..239abe7c2 100644
--- a/bramble-android/build.gradle
+++ b/bramble-android/build.gradle
@@ -8,11 +8,15 @@ android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
+ packagingOptions {
+ doNotStrip '**/*.so'
+ }
+
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
- versionCode 10216
- versionName "1.2.16"
+ versionCode 10218
+ versionName "1.2.18"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java
index 478d59425..9df713343 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java
@@ -175,6 +175,11 @@ class AndroidBluetoothPlugin
} catch (IOException e) {
IoUtils.tryToClose(s, LOG, WARNING);
throw e;
+ } catch (NullPointerException e) {
+ // BluetoothSocket#connect() may throw an NPE under unknown
+ // circumstances
+ IoUtils.tryToClose(s, LOG, WARNING);
+ throw new IOException(e);
}
}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
index 908d5bfb7..5fe56cd5c 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
@@ -114,12 +114,8 @@ public class AndroidUtils {
/**
* Returns an array of supported content types for image attachments.
- * GIFs can't be compressed on API < 24 so they're not supported.
- *
- * TODO: Remove this restriction when large message support is added
*/
public static String[] getSupportedImageContentTypes() {
- if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
- else return new String[] {"image/jpeg", "image/png", "image/gif"};
+ return new String[] {"image/jpeg", "image/png", "image/gif"};
}
}
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/bramble-core/src/main/resources/bridges b/bramble-core/src/main/resources/bridges
index 49e684c1e..586e64889 100644
--- a/bramble-core/src/main/resources/bridges
+++ b/bramble-core/src/main/resources/bridges
@@ -2,12 +2,11 @@ Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUV
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
-Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
+Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
-Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
diff --git a/bramble-java/build.gradle b/bramble-java/build.gradle
index 8b3211c9e..3d51e8fec 100644
--- a/bramble-java/build.gradle
+++ b/bramble-java/build.gradle
@@ -16,7 +16,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2'
- tor 'org.briarproject:tor:0.3.5.13@zip'
+ tor 'org.briarproject:tor:0.3.5.13-1@zip'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
index 35fe3cc90..d5efba41c 100644
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
@@ -25,6 +25,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
+import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux;
@@ -96,8 +97,15 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
String architecture = null;
if (isLinux()) {
String arch = System.getProperty("os.arch");
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("System's os.arch is " + arch);
+ }
if (arch.equals("amd64")) {
architecture = "linux-x86_64";
+ } else if (arch.equals("aarch64")) {
+ architecture = "linux-aarch64";
+ } else if (arch.equals("arm")) {
+ architecture = "linux-armhf";
}
}
if (architecture == null) {
@@ -105,6 +113,10 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
return null;
}
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("The selected architecture for Tor is " + architecture);
+ }
+
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
diff --git a/bramble-java/witness.gradle b/bramble-java/witness.gradle
index 02a7bb626..a6b8c8672 100644
--- a/bramble-java/witness.gradle
+++ b/bramble-java/witness.gradle
@@ -24,7 +24,7 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
- 'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0',
+ 'org.briarproject:tor:0.3.5.13-1:tor-0.3.5.13-1.zip:ef35c16bf8dc1f4c75ed71d9f55e4514f383d124ec96b859aca647c990927c99',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 501acff28..3a75bbbf1 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -19,12 +19,16 @@ android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
+ packagingOptions {
+ doNotStrip '**/*.so'
+ }
+
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
- versionCode 10216
- versionName "1.2.16"
- applicationId "org.briarproject.briar.socialbackup"
+ versionCode 10218
+ versionName "1.2.18"
+ applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "GitHash",
@@ -95,6 +99,7 @@ dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android')
+ implementation 'androidx.fragment:fragment:1.3.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index 863a7b3b1..f372502ce 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -27,7 +27,10 @@
-
+
@@ -38,6 +41,7 @@
android:name="org.briarproject.briar.android.BriarApplicationImpl"
android:allowBackup="false"
android:banner="@mipmap/tv_banner"
+ android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
@@ -374,7 +378,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 47cd279ba..45249f6de 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,8 +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.AddNearbyContactModule;
import org.briarproject.briar.android.forum.ForumModule;
-import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
+import org.briarproject.briar.android.introduction.IntroductionModule;
import org.briarproject.briar.android.logging.LoggingModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
@@ -75,7 +76,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,
@@ -84,6 +85,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
DevReportModule.class,
ContactListModule.class,
AndroidDarkCrystalModule.class,
+ IntroductionModule.class,
// below need to be within same scope as ViewModelProvider.Factory
ForumModule.class,
GroupListModule.class,
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java
index e51c9497d..94f5feb1f 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java
@@ -33,7 +33,6 @@ public class DozeFragment extends SetupFragment
private DozeView dozeView;
private HuaweiView huaweiView;
private Button next;
- private ProgressBar progressBar;
private boolean secondAttempt = false;
public static DozeFragment newInstance() {
@@ -58,11 +57,19 @@ public class DozeFragment extends SetupFragment
huaweiView = v.findViewById(R.id.huaweiView);
huaweiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
- progressBar = v.findViewById(R.id.progress);
+ ProgressBar progressBar = v.findViewById(R.id.progress);
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
next.setOnClickListener(this);
+ viewModel.getIsCreatingAccount()
+ .observe(getViewLifecycleOwner(), isCreatingAccount -> {
+ if (isCreatingAccount) {
+ next.setVisibility(INVISIBLE);
+ progressBar.setVisibility(VISIBLE);
+ }
+ });
+
return v;
}
@@ -104,15 +111,6 @@ public class DozeFragment extends SetupFragment
@Override
public void onClick(View view) {
- setNextClicked();
viewModel.dozeExceptionConfirmed();
}
-
- @Override
- void setNextClicked() {
- super.setNextClicked();
-
- next.setVisibility(INVISIBLE);
- progressBar.setVisibility(VISIBLE);
- }
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java
index d7cb2b385..94dc5fb13 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java
@@ -38,7 +38,6 @@ public class SetPasswordFragment extends SetupFragment {
private TextInputEditText passwordConfirmation;
private StrengthMeter strengthMeter;
private Button nextButton;
- private ProgressBar progressBar;
public static SetPasswordFragment newInstance() {
return new SetPasswordFragment();
@@ -64,7 +63,7 @@ public class SetPasswordFragment extends SetupFragment {
v.findViewById(R.id.password_confirm_wrapper);
passwordConfirmation = v.findViewById(R.id.password_confirm);
nextButton = v.findViewById(R.id.next);
- progressBar = v.findViewById(R.id.progress);
+ ProgressBar progressBar = v.findViewById(R.id.progress);
passwordEntry.addTextChangedListener(this);
passwordConfirmation.addTextChangedListener(this);
@@ -75,6 +74,17 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
+ viewModel.getIsCreatingAccount()
+ .observe(getViewLifecycleOwner(), isCreatingAccount -> {
+ if (isCreatingAccount) {
+ nextButton.setVisibility(INVISIBLE);
+ progressBar.setVisibility(VISIBLE);
+ // this also avoids the keyboard popping up
+ passwordEntry.setFocusable(false);
+ passwordConfirmation.setFocusable(false);
+ }
+ });
+
return v;
}
@@ -116,20 +126,6 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
-
- setNextClicked();
viewModel.setPassword(passwordEntry.getText().toString());
}
-
- @Override
- void setNextClicked() {
- super.setNextClicked();
-
- passwordEntry.setFocusable(false);
- passwordConfirmation.setFocusable(false);
- if (!viewModel.needToShowDozeFragment()) {
- nextButton.setVisibility(INVISIBLE);
- progressBar.setVisibility(VISIBLE);
- }
- }
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java
index dac85947b..decd7d009 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupFragment.java
@@ -7,7 +7,6 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -19,8 +18,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
@@ -35,7 +32,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
- private boolean clicked = false;
@Inject
ViewModelProvider.Factory viewModelFactory;
@@ -48,27 +44,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
.get(SetupViewModel.class);
}
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
- }
- if (clicked) {
- setNextClicked();
- }
- }
-
- @Override
- public void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(STATE_KEY_CLICKED, clicked);
- }
-
- @CallSuper
- void setNextClicked() {
- this.clicked = true;
- }
-
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu);
@@ -114,5 +89,4 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
public void afterTextChanged(Editable editable) {
// noop
}
-
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java
index d659e9475..322d4fd24 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupViewModel.java
@@ -17,6 +17,8 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
@@ -36,6 +38,8 @@ class SetupViewModel extends AndroidViewModel {
@Nullable
private String authorName, password;
private final MutableLiveEvent state = new MutableLiveEvent<>();
+ private final MutableLiveData isCreatingAccount =
+ new MutableLiveData<>(false);
private final AccountManager accountManager;
private final Executor ioExecutor;
@@ -67,6 +71,10 @@ class SetupViewModel extends AndroidViewModel {
return state;
}
+ LiveData getIsCreatingAccount() {
+ return isCreatingAccount;
+ }
+
void setAuthorName(String authorName) {
this.authorName = authorName;
state.setEvent(SET_PASSWORD);
@@ -97,6 +105,7 @@ class SetupViewModel extends AndroidViewModel {
private void createAccount() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
+ isCreatingAccount.setValue(true);
ioExecutor.execute(() -> {
if (accountManager.createAccount(authorName, password)) {
LOG.info("Created account");
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 71e9d9265..cc99c4db7 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
@@ -24,6 +24,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.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;
@@ -39,10 +43,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.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.login.ChangePasswordActivity;
import org.briarproject.briar.android.login.OpenDatabaseFragment;
import org.briarproject.briar.android.login.PasswordFragment;
@@ -122,9 +122,7 @@ public interface ActivityComponent {
void inject(PanicPreferencesActivity activity);
- void inject(ContactExchangeActivity activity);
-
- void inject(KeyAgreementActivity activity);
+ void inject(AddNearbyContactActivity activity);
void inject(ConversationActivity activity);
@@ -226,7 +224,9 @@ public interface ActivityComponent {
void inject(FeedFragment fragment);
- void inject(KeyAgreementFragment fragment);
+ void inject(AddNearbyContactIntroFragment fragment);
+
+ void inject(AddNearbyContactFragment fragment);
void inject(LinkExchangeFragment fragment);
@@ -244,7 +244,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/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java
index 58ab2ef0c..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,9 +9,7 @@ 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_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/attachment/AttachmentRetrieverImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetrieverImpl.java
index 9f8c3cc15..eeb1e37a0 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetrieverImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/attachment/AttachmentRetrieverImpl.java
@@ -28,9 +28,12 @@ import javax.inject.Inject;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import static java.util.Arrays.asList;
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.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
@@ -86,7 +89,17 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
List headers = messageHeader.getAttachmentHeaders();
List> items = new ArrayList<>(headers.size());
boolean needsSize = headers.size() == 1;
+ List supported = asList(getSupportedImageContentTypes());
for (AttachmentHeader h : headers) {
+ // Fail early if we don't support the content type
+ if (!supported.contains(h.getContentType())) {
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("Unsupported content type " + h.getContentType());
+ }
+ AttachmentItem item = new AttachmentItem(h, "", ERROR);
+ items.add(new MutableLiveData<>(item));
+ continue;
+ }
// try cache for existing item live data
MutableLiveData liveData =
itemsWithSize.get(h.getMessageId());
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java
index 056d4c77a..d2e63e174 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/BaseContactListAdapter.java
@@ -1,7 +1,6 @@
package org.briarproject.briar.android.contact;
import android.content.Context;
-import android.view.View;
import org.briarproject.briar.android.util.BriarAdapter;
@@ -45,8 +44,4 @@ public abstract class BaseContactListAdapter {
- void onItemClick(View view, I item);
- }
-
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java
index ffdf9d6ec..622bfe007 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactItemViewHolder.java
@@ -7,7 +7,6 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import javax.annotation.Nullable;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java
index 13b64815e..103bc46c4 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListAdapter.java
@@ -7,7 +7,6 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.R;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
@@ -16,9 +15,6 @@ import androidx.recyclerview.widget.ListAdapter;
public class ContactListAdapter extends
ListAdapter {
- // TODO: using the click listener interface from BaseContactListAdapter on
- // purpose here because it is entangled with ContactListItemViewHolder. At
- // some point we probably want to change that.
protected final OnContactClickListener listener;
public ContactListAdapter(
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 1a803c9a6..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,12 +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.BaseContactListAdapter.OnContactClickListener;
+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.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
@@ -34,7 +33,6 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
-import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@MethodsNotNullByDefault
@@ -102,7 +100,6 @@ public class ContactListFragment extends BaseFragment
.observe(getViewLifecycleOwner(), result -> {
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
- if (requireNonNull(items).size() == 0) list.showData();
});
});
viewModel.getHasPendingContacts()
@@ -128,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/ContactListItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java
index 5b5fa172a..149a26972 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListItemViewHolder.java
@@ -5,7 +5,6 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import java.util.Locale;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java
index 71a1c5740..f8e0184e3 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListViewModel.java
@@ -3,70 +3,41 @@ package org.briarproject.briar.android.contact;
import android.app.Application;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
-import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
-import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
-import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
-import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
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.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
-import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
-import org.briarproject.briar.android.viewmodel.DbViewModel;
-import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
-import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
-import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
-import org.briarproject.briar.api.conversation.ConversationMessageHeader;
-import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
-import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
-import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
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.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
-import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
-class ContactListViewModel extends DbViewModel implements EventListener {
+class ContactListViewModel extends ContactsViewModel {
private static final Logger LOG =
getLogger(ContactListViewModel.class.getName());
- private final ContactManager contactManager;
- private final AuthorManager authorManager;
- private final ConversationManager conversationManager;
- private final ConnectionRegistry connectionRegistry;
- private final EventBus eventBus;
private final AndroidNotificationManager notificationManager;
- private final MutableLiveData>>
- contactListItems = new MutableLiveData<>();
-
private final MutableLiveData hasPendingContacts =
new MutableLiveData<>();
@@ -79,99 +50,25 @@ class ContactListViewModel extends DbViewModel implements EventListener {
ConversationManager conversationManager,
ConnectionRegistry connectionRegistry, EventBus eventBus,
AndroidNotificationManager notificationManager) {
- super(application, dbExecutor, lifecycleManager, db, androidExecutor);
- this.contactManager = contactManager;
- this.authorManager = authorManager;
- this.conversationManager = conversationManager;
- this.connectionRegistry = connectionRegistry;
- this.eventBus = eventBus;
+ super(application, dbExecutor, lifecycleManager, db, androidExecutor,
+ contactManager, authorManager, conversationManager,
+ connectionRegistry, eventBus);
this.notificationManager = notificationManager;
- this.eventBus.addListener(this);
- }
-
- @Override
- protected void onCleared() {
- super.onCleared();
- eventBus.removeListener(this);
- }
-
- void loadContacts() {
- loadList(this::loadContacts, contactListItems::setValue);
- }
-
- private List loadContacts(Transaction txn)
- throws DbException {
- long start = now();
- List contacts = new ArrayList<>();
- for (Contact c : contactManager.getContacts(txn)) {
- ContactId id = c.getId();
- AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
- MessageTracker.GroupCount count =
- conversationManager.getGroupCount(txn, id);
- boolean connected = connectionRegistry.isConnected(c.getId());
- contacts.add(new ContactListItem(c, authorInfo, connected, count));
- }
- Collections.sort(contacts);
- logDuration(LOG, "Full load", start);
- return contacts;
}
@Override
public void eventOccurred(Event e) {
- if (e instanceof ContactAddedEvent) {
- LOG.info("Contact added, reloading");
- loadContacts();
- } else if (e instanceof ContactConnectedEvent) {
- updateItem(((ContactConnectedEvent) e).getContactId(),
- item -> new ContactListItem(item, true), false);
- } else if (e instanceof ContactDisconnectedEvent) {
- updateItem(((ContactDisconnectedEvent) e).getContactId(),
- item -> new ContactListItem(item, false), false);
- } else if (e instanceof ContactRemovedEvent) {
- LOG.info("Contact removed, removing item");
- removeItem(((ContactRemovedEvent) e).getContactId());
- } else if (e instanceof ConversationMessageReceivedEvent) {
- LOG.info("Conversation message received, updating item");
- ConversationMessageReceivedEvent> p =
- (ConversationMessageReceivedEvent>) e;
- ConversationMessageHeader h = p.getMessageHeader();
- updateItem(p.getContactId(), item -> new ContactListItem(item, h),
- true);
- } else if (e instanceof PendingContactAddedEvent ||
+ super.eventOccurred(e);
+ if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
- } else if (e instanceof AvatarUpdatedEvent) {
- AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
- updateItem(a.getContactId(), item -> new ContactListItem(item,
- a.getAttachmentHeader()), false);
}
}
- LiveData>> getContactListItems() {
- return contactListItems;
- }
-
LiveData getHasPendingContacts() {
return hasPendingContacts;
}
- private void updateItem(ContactId c,
- Function replacer, boolean sort) {
- List list = updateListItems(contactListItems,
- itemToTest -> itemToTest.getContact().getId().equals(c),
- replacer);
- if (list == null) return;
- if (sort) Collections.sort(list);
- contactListItems.setValue(new LiveResult<>(list));
- }
-
- private void removeItem(ContactId c) {
- List list = removeListItems(contactListItems,
- itemToTest -> itemToTest.getContact().getId().equals(c));
- if (list == null) return;
- contactListItems.setValue(new LiveResult<>(list));
- }
-
void checkForPendingContacts() {
runOnDbThread(() -> {
try {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactsViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactsViewModel.java
new file mode 100644
index 000000000..e0b1ab910
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactsViewModel.java
@@ -0,0 +1,170 @@
+package org.briarproject.briar.android.contact;
+
+import android.app.Application;
+
+import org.briarproject.bramble.api.connection.ConnectionRegistry;
+import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
+import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
+import org.briarproject.bramble.api.db.DatabaseExecutor;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.db.TransactionManager;
+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.lifecycle.LifecycleManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
+import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
+import org.briarproject.bramble.api.system.AndroidExecutor;
+import org.briarproject.briar.android.viewmodel.DbViewModel;
+import org.briarproject.briar.android.viewmodel.LiveResult;
+import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
+import org.briarproject.briar.api.client.MessageTracker;
+import org.briarproject.briar.api.conversation.ConversationManager;
+import org.briarproject.briar.api.conversation.ConversationMessageHeader;
+import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
+import org.briarproject.briar.api.identity.AuthorInfo;
+import org.briarproject.briar.api.identity.AuthorManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import androidx.annotation.UiThread;
+import androidx.arch.core.util.Function;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.util.LogUtils.logDuration;
+import static org.briarproject.bramble.util.LogUtils.now;
+
+@NotNullByDefault
+public class ContactsViewModel extends DbViewModel implements EventListener {
+
+ private static final Logger LOG =
+ getLogger(ContactsViewModel.class.getName());
+
+ protected final ContactManager contactManager;
+ private final AuthorManager authorManager;
+ private final ConversationManager conversationManager;
+ private final ConnectionRegistry connectionRegistry;
+ private final EventBus eventBus;
+
+ private final MutableLiveData>>
+ contactListItems = new MutableLiveData<>();
+
+ @Inject
+ public ContactsViewModel(Application application,
+ @DatabaseExecutor Executor dbExecutor,
+ LifecycleManager lifecycleManager, TransactionManager db,
+ AndroidExecutor androidExecutor, ContactManager contactManager,
+ AuthorManager authorManager,
+ ConversationManager conversationManager,
+ ConnectionRegistry connectionRegistry, EventBus eventBus) {
+ super(application, dbExecutor, lifecycleManager, db, androidExecutor);
+ this.contactManager = contactManager;
+ this.authorManager = authorManager;
+ this.conversationManager = conversationManager;
+ this.connectionRegistry = connectionRegistry;
+ this.eventBus = eventBus;
+ this.eventBus.addListener(this);
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ eventBus.removeListener(this);
+ }
+
+ protected void loadContacts() {
+ loadList(this::loadContacts, contactListItems::setValue);
+ }
+
+ private List loadContacts(Transaction txn)
+ throws DbException {
+ long start = now();
+ List contacts = new ArrayList<>();
+ for (Contact c : contactManager.getContacts(txn)) {
+ ContactId id = c.getId();
+ if (!displayContact(id)) {
+ continue;
+ }
+ AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
+ MessageTracker.GroupCount count =
+ conversationManager.getGroupCount(txn, id);
+ boolean connected = connectionRegistry.isConnected(c.getId());
+ contacts.add(new ContactListItem(c, authorInfo, connected, count));
+ }
+ Collections.sort(contacts);
+ logDuration(LOG, "Full load", start);
+ return contacts;
+ }
+
+ /**
+ * Override this method to display only a subset of contacts.
+ */
+ protected boolean displayContact(ContactId contactId) {
+ return true;
+ }
+
+ @Override
+ public void eventOccurred(Event e) {
+ if (e instanceof ContactAddedEvent) {
+ LOG.info("Contact added, reloading");
+ loadContacts();
+ } else if (e instanceof ContactConnectedEvent) {
+ updateItem(((ContactConnectedEvent) e).getContactId(),
+ item -> new ContactListItem(item, true), false);
+ } else if (e instanceof ContactDisconnectedEvent) {
+ updateItem(((ContactDisconnectedEvent) e).getContactId(),
+ item -> new ContactListItem(item, false), false);
+ } else if (e instanceof ContactRemovedEvent) {
+ LOG.info("Contact removed, removing item");
+ removeItem(((ContactRemovedEvent) e).getContactId());
+ } else if (e instanceof ConversationMessageReceivedEvent) {
+ LOG.info("Conversation message received, updating item");
+ ConversationMessageReceivedEvent> p =
+ (ConversationMessageReceivedEvent>) e;
+ ConversationMessageHeader h = p.getMessageHeader();
+ updateItem(p.getContactId(), item -> new ContactListItem(item, h),
+ true);
+ } else if (e instanceof AvatarUpdatedEvent) {
+ AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
+ updateItem(a.getContactId(), item -> new ContactListItem(item,
+ a.getAttachmentHeader()), false);
+ }
+ }
+
+ public LiveData>> getContactListItems() {
+ return contactListItems;
+ }
+
+ @UiThread
+ private void updateItem(ContactId c,
+ Function replacer, boolean sort) {
+ List list = updateListItems(contactListItems,
+ itemToTest -> itemToTest.getContact().getId().equals(c),
+ replacer);
+ if (list == null) return;
+ if (sort) Collections.sort(list);
+ contactListItems.setValue(new LiveResult<>(list));
+ }
+
+ @UiThread
+ private void removeItem(ContactId c) {
+ List list = removeListItems(contactListItems,
+ itemToTest -> itemToTest.getContact().getId().equals(c));
+ if (list == null) return;
+ contactListItems.setValue(new LiveResult<>(list));
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/LegacyContactListAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/LegacyContactListAdapter.java
deleted file mode 100644
index e8777bbdb..000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/LegacyContactListAdapter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.briarproject.briar.android.contact;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.R;
-
-@NotNullByDefault
-public class LegacyContactListAdapter extends
- BaseContactListAdapter {
-
- public LegacyContactListAdapter(Context context,
- OnContactClickListener listener) {
- super(context, ContactListItem.class, listener);
- }
-
- @Override
- public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
- int i) {
- View v = LayoutInflater.from(viewGroup.getContext()).inflate(
- R.layout.list_item_contact, viewGroup, false);
-
- return new ContactListItemViewHolder(v);
- }
-
- @Override
- public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
- // check for all properties that influence visual
- // representation of contact
- if (c1.isEmpty() != c2.isEmpty()) {
- return false;
- }
- if (c1.getUnreadCount() != c2.getUnreadCount()) {
- return false;
- }
- if (c1.getTimestamp() != c2.getTimestamp()) {
- return false;
- }
- return c1.isConnected() == c2.isConnected();
- }
-
- @Override
- public int compare(ContactListItem c1, ContactListItem c2) {
- return Long.compare(c2.getTimestamp(), c1.getTimestamp());
- }
-
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/OnContactClickListener.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/OnContactClickListener.java
new file mode 100644
index 000000000..8381c1923
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/OnContactClickListener.java
@@ -0,0 +1,9 @@
+package org.briarproject.briar.android.contact;
+
+import android.view.View;
+
+public interface OnContactClickListener {
+
+ void onItemClick(View view, I item);
+
+}
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/AddNearbyContactActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java
new file mode 100644
index 000000000..1fdbcd550
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactActivity.java
@@ -0,0 +1,226 @@
+package org.briarproject.briar.android.contact.add.nearby;
+
+import android.content.Intent;
+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.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.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;
+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.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
+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.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.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED;
+import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class AddNearbyContactActivity extends BriarActivity
+ implements BaseFragmentListener {
+
+ private static final Logger LOG =
+ getLogger(AddNearbyContactActivity.class.getName());
+
+ @Inject
+ 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);
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+ viewModel = new ViewModelProvider(this, viewModelFactory)
+ .get(AddNearbyContactViewModel.class);
+ permissionManager = new AddNearbyContactPermissionManager(this,
+ permissionLauncher::launch, viewModel.isBluetoothSupported());
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle state) {
+ super.onCreate(state);
+ setContentView(R.layout.activity_fragment_container_toolbar);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
+ 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 -> {
+ if (show) showQrCodeFragment();
+ });
+ requireNonNull(getSupportActionBar())
+ .setTitle(R.string.add_contact_title);
+ viewModel.getState()
+ .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();
+ 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");
+ viewModel.setBluetoothDecision(ACCEPTED);
+ } else {
+ LOG.info("Bluetooth discoverability was refused");
+ viewModel.setBluetoothDecision(REFUSED);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (viewModel.getState().getValue() instanceof Failed) {
+ // 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();
+ }
+ }
+
+ private void requestBluetoothDiscoverable() {
+ if (!viewModel.isBluetoothSupported()) {
+ 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.setBluetoothDecision(BluetoothDecision.WAITING);
+ bluetoothLauncher.launch(120); // 2min discoverable
+ } else {
+ viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER);
+ }
+ }
+ }
+
+ private void showQrCodeFragment() {
+ // FIXME #824
+ FragmentManager fm = getSupportFragmentManager();
+ if (fm.findFragmentByTag(AddNearbyContactFragment.TAG) == null) {
+ BaseFragment f = AddNearbyContactFragment.newInstance();
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, f, f.getUniqueTag())
+ .addToBackStack(f.getUniqueTag())
+ .commit();
+ }
+ }
+
+ private void onAddContactStateChanged(AddContactState state) {
+ if (state instanceof ContactExchangeFinished) {
+ ContactExchangeResult result =
+ ((ContactExchangeFinished) state).result;
+ onContactExchangeResult(result);
+ } else if (state instanceof Failed) {
+ 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());
+ }
+
+}
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/AddNearbyContactErrorFragment.java
similarity index 86%
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/AddNearbyContactErrorFragment.java
index be708d73c..15dc04848 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/AddNearbyContactErrorFragment.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;
@@ -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/AddNearbyContactFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java
new file mode 100644
index 000000000..6f0a841cb
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactFragment.java
@@ -0,0 +1,202 @@
+package org.briarproject.briar.android.contact.add.nearby;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.TextView;
+import android.widget.Toast;
+
+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.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;
+
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+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;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.Toast.LENGTH_LONG;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.LogUtils.logException;
+
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
+public class AddNearbyContactFragment extends BaseFragment
+ implements QrCodeView.FullscreenListener {
+
+ static final String TAG = AddNearbyContactFragment.class.getName();
+
+ private static final Logger LOG = Logger.getLogger(TAG);
+
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private AddNearbyContactViewModel viewModel;
+ private CameraView cameraView;
+ private LinearLayout cameraOverlay;
+ private View statusView;
+ private QrCodeView qrCodeView;
+ private TextView status;
+
+ public static AddNearbyContactFragment newInstance() {
+ Bundle args = new Bundle();
+ AddNearbyContactFragment fragment = new AddNearbyContactFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
+ .get(AddNearbyContactViewModel.class);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_keyagreement_qr, container,
+ false);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ cameraView = view.findViewById(R.id.camera_view);
+ 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);
+
+ viewModel.getState().observe(getViewLifecycleOwner(),
+ this::onAddContactStateChanged);
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
+ cameraView.setPreviewConsumer(viewModel.qrCodeDecoder);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ try {
+ cameraView.start();
+ } catch (CameraException e) {
+ logCameraExceptionAndFinish(e);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ try {
+ cameraView.stop();
+ } catch (CameraException e) {
+ logCameraExceptionAndFinish(e);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ requireActivity()
+ .setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+ super.onDestroy();
+ }
+
+ @Override
+ public void setFullscreen(boolean fullscreen) {
+ LinearLayout.LayoutParams statusParams, qrCodeParams;
+ if (fullscreen) {
+ // Grow the QR code view to fill its parent
+ statusParams = new LayoutParams(0, 0, 0f);
+ qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
+ } else {
+ // Shrink the QR code view to fill half its parent
+ if (cameraOverlay.getOrientation() == HORIZONTAL) {
+ statusParams = new LayoutParams(0, MATCH_PARENT, 1f);
+ qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f);
+ } else {
+ statusParams = new LayoutParams(MATCH_PARENT, 0, 1f);
+ qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f);
+ }
+ }
+ statusView.setLayoutParams(statusParams);
+ qrCodeView.setLayoutParams(qrCodeParams);
+ cameraOverlay.invalidate();
+ }
+
+ @UiThread
+ private void onAddContactStateChanged(AddContactState state) {
+ if (state instanceof AddContactState.KeyAgreementListening) {
+ Bitmap qrCode =
+ ((AddContactState.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);
+ status.setText(R.string.authenticating_with_device);
+ } else if (state instanceof ContactExchangeStarted) {
+ 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);
+ Toast.makeText(getActivity(), R.string.camera_error,
+ LENGTH_LONG).show();
+ finish();
+ }
+
+ @Override
+ protected void finish() {
+ requireActivity().getSupportFragmentManager().popBackStack();
+ }
+
+}
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/AddNearbyContactIntroFragment.java
similarity index 58%
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/AddNearbyContactIntroFragment.java
index 6ee1f45c2..7bc666550 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/AddNearbyContactIntroFragment.java
@@ -1,6 +1,5 @@
-package org.briarproject.briar.android.keyagreement;
+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,43 +9,42 @@ 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;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
-public class IntroFragment extends BaseFragment {
+public class AddNearbyContactIntroFragment extends BaseFragment {
- interface IntroScreenSeenListener {
- void showNextScreen();
- }
+ public static final String TAG = AddNearbyContactIntroFragment.class.getName();
- public static final String TAG = IntroFragment.class.getName();
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private AddNearbyContactViewModel viewModel;
- private IntroScreenSeenListener screenSeenListener;
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;
}
@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(AddNearbyContactViewModel.class);
}
@Nullable
@@ -59,7 +57,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 +67,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/keyagreement/ContactExchangeModule.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactModule.java
similarity index 55%
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/AddNearbyContactModule.java
index 05a539d5c..79dfaa69c 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/AddNearbyContactModule.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;
@@ -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/AddNearbyContactPermissionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java
new file mode 100644
index 000000000..426da7cdc
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactPermissionManager.java
@@ -0,0 +1,168 @@
+package org.briarproject.briar.android.contact.add.nearby;
+
+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.util.Consumer;
+
+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.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 Consumer requestPermissions;
+ private final boolean isBluetoothSupported;
+
+ AddNearbyContactPermissionManager(BaseActivity ctx,
+ Consumer requestPermissions,
+ boolean isBluetoothSupported) {
+ this.ctx = ctx;
+ this.requestPermissions = requestPermissions;
+ this.isBluetoothSupported = isBluetoothSupported;
+ }
+
+ void resetPermissions() {
+ cameraPermission = Permission.UNKNOWN;
+ 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 ||
+ !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};
+ }
+ requestPermissions.accept(permissions);
+ }
+
+ void onRequestPermissionResult(Map result,
+ Runnable onPermissionsGranted) {
+ if (gotPermission(CAMERA, result)) {
+ cameraPermission = Permission.GRANTED;
+ } else if (shouldShowRationale(CAMERA)) {
+ cameraPermission = Permission.SHOW_RATIONALE;
+ } else {
+ cameraPermission = Permission.PERMANENTLY_DENIED;
+ }
+ if (isBluetoothSupported) {
+ if (gotPermission(ACCESS_FINE_LOCATION, result)) {
+ 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,
+ 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) {
+ 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
new file mode 100644
index 000000000..40650d189
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java
@@ -0,0 +1,520 @@
+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;
+
+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;
+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.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.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.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;
+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;
+
+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.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;
+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.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;
+
+@NotNullByDefault
+class AddNearbyContactViewModel extends AndroidViewModel
+ implements EventListener, QrCodeDecoder.ResultCallback {
+
+ private static final Logger LOG =
+ getLogger(AddNearbyContactViewModel.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 AndroidExecutor androidExecutor;
+ 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;
+
+ private final MutableLiveEvent checkPermissions =
+ new MutableLiveEvent<>();
+ 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
+ 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
+ * 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;
+
+ @Inject
+ AddNearbyContactViewModel(Application app,
+ EventBus eventBus,
+ AndroidExecutor androidExecutor,
+ @IoExecutor Executor ioExecutor,
+ PluginManager pluginManager,
+ PayloadEncoder payloadEncoder,
+ PayloadParser payloadParser,
+ Provider keyAgreementTaskProvider,
+ ContactExchangeManager contactExchangeManager,
+ ConnectionManager connectionManager) {
+ super(app);
+ this.eventBus = eventBus;
+ this.androidExecutor = androidExecutor;
+ 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(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();
+ }
+
+ @UiThread
+ void onContinueClicked() {
+ if (bluetoothDecision == REFUSED) {
+ bluetoothDecision = UNKNOWN; // Ask again
+ }
+ wasContinueClicked = true;
+ checkPermissions.setEvent(true);
+ showQrCodeFragmentIfAllowed();
+ }
+
+ @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 = 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
+ 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();
+ });
+ }
+
+ @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();
+ }
+ } else 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) {
+ LOG.info("KeyAgreementStartedEvent received");
+ state.setValue(new KeyAgreementStarted());
+ } else if (e instanceof KeyAgreementFinishedEvent) {
+ 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 AddContactState.Failed());
+ } else if (e instanceof KeyAgreementFailedEvent) {
+ LOG.info("KeyAgreementFailedEvent received");
+ resetPayloadFlags();
+ state.setValue(new AddContactState.Failed());
+ }
+ }
+
+ @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.
+ */
+ 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 AddContactState.QrCodeScanned());
+ } catch (UnsupportedVersionException e) {
+ resetPayloadFlags();
+ state.postValue(new AddContactState.Failed(e.isTooOld()));
+ } catch (IOException | IllegalArgumentException e) {
+ LOG.log(WARNING, "QR Code Invalid", e);
+ androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
+ R.string.qr_code_invalid, LENGTH_LONG).show());
+ resetPayloadFlags();
+ state.postValue(new AddContactState.Failed());
+ }
+ }
+
+ private void resetPayloadFlags() {
+ gotRemotePayload = false;
+ gotLocalPayload = false;
+ }
+
+ @UiThread
+ 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,
+ masterKey, alice, true);
+ // Reuse the connection as a transport connection
+ connectionManager
+ .manageOutgoingConnection(contact.getId(), t, conn);
+ Success success = new Success(contact.getAuthor());
+ state.postValue(new ContactExchangeFinished(success));
+ } catch (ContactExistsException e) {
+ tryToClose(conn);
+ Error error = new Error(e.getRemoteAuthor());
+ state.postValue(new ContactExchangeFinished(error));
+ } catch (DbException | IOException e) {
+ tryToClose(conn);
+ logException(LOG, WARNING, e);
+ Error error = new Error(null);
+ state.postValue(new ContactExchangeFinished(error));
+ }
+ });
+ }
+
+ private class BluetoothStateReceiver extends BroadcastReceiver {
+ @UiThread
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, -1);
+ LOG.info("Bluetooth scan mode changed: " + scanMode);
+ showQrCodeFragmentIfAllowed();
+ }
+ }
+
+ private void tryToClose(DuplexTransportConnection conn) {
+ try {
+ conn.getReader().dispose(true, true);
+ conn.getWriter().dispose(true);
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
+ }
+ }
+
+ /**
+ * 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;
+ showQrCodeFragmentIfAllowed();
+ }
+
+ LiveEvent getCheckPermissions() {
+ return checkPermissions;
+ }
+
+ LiveEvent getRequestBluetoothDiscoverable() {
+ return requestBluetoothDiscoverable;
+ }
+
+ LiveEvent getShowQrCodeFragment() {
+ return showQrCodeFragment;
+ }
+
+ LiveData getState() {
+ return state;
+ }
+
+}
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 98%
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..7a5720459 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,8 +125,14 @@ 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();
+ 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
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 78%
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..bbb9dc875 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,10 +1,9 @@
-package org.briarproject.briar.android.keyagreement;
+package org.briarproject.briar.android.contact.add.nearby;
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,13 @@ 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 org.briarproject.bramble.api.system.AndroidExecutor;
+import java.util.concurrent.Executor;
import java.util.logging.Logger;
import androidx.annotation.UiThread;
@@ -26,22 +28,26 @@ 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 AndroidExecutor androidExecutor;
+ 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(AndroidExecutor androidExecutor,
+ @IoExecutor Executor ioExecutor, ResultCallback callback) {
+ this.androidExecutor = androidExecutor;
+ this.ioExecutor = ioExecutor;
this.callback = callback;
}
@@ -74,8 +80,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 +94,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();
+ androidExecutor.runOnUiThread(this::askForPreviewFrame);
}
- callback.handleResult(result);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- askForPreviewFrame();
- }
+ });
}
private static BinaryBitmap binarize(byte[] data, int width, int height,
@@ -143,7 +128,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@NotNullByDefault
interface ResultCallback {
-
- void handleResult(Result result);
+ @IoExecutor
+ void onQrCodeDecoded(Result result);
}
}
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/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java
index f6252717e..6f9f7ddca 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorAdapter.java
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.contact.BaseContactListAdapter;
import org.briarproject.briar.android.contact.ContactItemViewHolder;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java
index f3affaa2d..3b0cb8751 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseContactSelectorFragment.java
@@ -12,8 +12,8 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.ContactItemViewHolder;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.BriarRecyclerView;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java
index df10f082c..1e4388c26 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/BaseSelectableContactHolder.java
@@ -6,8 +6,8 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.ContactItemViewHolder;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import javax.annotation.Nullable;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java
index 39144d3d6..97c45b5c5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorAdapter.java
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
+import org.briarproject.briar.android.contact.OnContactClickListener;
@NotNullByDefault
class ContactSelectorAdapter extends
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java
index 59e560021..ff88a8631 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/ContactSelectorFragment.java
@@ -8,7 +8,7 @@ import android.view.MenuItem;
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.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.briar.android.contact.OnContactClickListener;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java
index cf0d2cdab..5ec6c782c 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contactselection/SelectableContactHolder.java
@@ -3,7 +3,7 @@ package org.briarproject.briar.android.contactselection;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import javax.annotation.Nullable;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java
index 5b816f26c..a181b66f8 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java
@@ -24,10 +24,7 @@ import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.PullDownLayout;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.List;
-import java.util.Locale;
import javax.inject.Inject;
@@ -293,13 +290,10 @@ public class ImageActivity extends BriarActivity
@RequiresApi(api = 19)
private Intent getCreationIntent() {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
- Locale.getDefault());
- String fileName = sdf.format(new Date());
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addCategory(CATEGORY_OPENABLE);
intent.setType(getVisibleAttachment().getMimeType());
- intent.putExtra(EXTRA_TITLE, fileName);
+ intent.putExtra(EXTRA_TITLE, viewModel.getFileName());
return intent;
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java
index ca0b75179..80ab00150 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java
@@ -225,8 +225,8 @@ public class ImageViewModel extends DbViewModel implements EventListener {
});
}
- private String getFileName() {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
+ String getFileName() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd",
Locale.getDefault());
return sdf.format(new Date());
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/fragment/BaseFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/fragment/BaseFragment.java
index 0988e6147..22f1be68d 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/fragment/BaseFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/fragment/BaseFragment.java
@@ -10,9 +10,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.activity.ActivityComponent;
-import javax.annotation.Nullable;
-
import androidx.annotation.CallSuper;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -47,13 +46,11 @@ public abstract class BaseFragment extends Fragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- listener.onBackPressed();
- return true;
- default:
- return super.onOptionsItemSelected(item);
+ if (item.getItemId() == android.R.id.home) {
+ requireActivity().onBackPressed();
+ return true;
}
+ return super.onOptionsItemSelected(item);
}
@UiThread
@@ -79,6 +76,7 @@ public abstract class BaseFragment extends Fragment
void handleException(Exception e);
}
+ @Deprecated
@CallSuper
@Override
public void runOnUiThreadUnlessDestroyed(Runnable r) {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java
index 7a655f2af..389a30fb6 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactChooserFragment.java
@@ -5,74 +5,41 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import org.briarproject.bramble.api.connection.ConnectionRegistry;
-import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.contact.ContactManager;
-import org.briarproject.bramble.api.db.DbException;
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.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.briar.android.contact.ContactListAdapter;
import org.briarproject.briar.android.contact.ContactListItem;
-import org.briarproject.briar.android.contact.LegacyContactListAdapter;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.BriarRecyclerView;
-import org.briarproject.briar.api.client.MessageTracker.GroupCount;
-import org.briarproject.briar.api.conversation.ConversationManager;
-import org.briarproject.briar.api.identity.AuthorInfo;
-import org.briarproject.briar.api.identity.AuthorManager;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.util.LogUtils.logException;
-import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
-
-@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
-public class ContactChooserFragment extends BaseFragment {
+public class ContactChooserFragment extends BaseFragment
+ implements OnContactClickListener {
- public static final String TAG = ContactChooserFragment.class.getName();
- private static final Logger LOG = Logger.getLogger(TAG);
+ private static final String TAG = ContactChooserFragment.class.getName();
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private IntroductionViewModel viewModel;
+ private final ContactListAdapter adapter = new ContactListAdapter(this);
private BriarRecyclerView list;
- private LegacyContactListAdapter adapter;
- private ContactId contactId;
-
- // Fields that are accessed from background threads must be volatile
- private volatile Contact c1;
- @Inject
- volatile ContactManager contactManager;
- @Inject
- volatile AuthorManager authorManager;
- @Inject
- volatile ConversationManager conversationManager;
- @Inject
- volatile ConnectionRegistry connectionRegistry;
-
- public static ContactChooserFragment newInstance(ContactId id) {
- Bundle args = new Bundle();
-
- ContactChooserFragment fragment = new ContactChooserFragment();
- args.putInt(CONTACT_ID, id.getInt());
- fragment.setArguments(args);
- return fragment;
- }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
+ viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
+ .get(IntroductionViewModel.class);
}
@Override
@@ -80,23 +47,20 @@ public class ContactChooserFragment extends BaseFragment {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- View contentView = inflater.inflate(R.layout.list, container, false);
+ // change toolbar text (relevant when navigating back to this fragment)
+ requireActivity().setTitle(R.string.introduction_activity_title);
- OnContactClickListener onContactClickListener =
- (view, item) -> {
- if (c1 == null) throw new IllegalStateException();
- Contact c2 = item.getContact();
- showMessageScreen(c1, c2);
- };
- adapter = new LegacyContactListAdapter(requireActivity(),
- onContactClickListener);
+ View contentView = inflater.inflate(R.layout.list, container, false);
list = contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(R.string.no_contacts);
- contactId = new ContactId(requireArguments().getInt(CONTACT_ID));
+ viewModel.getContactListItems().observe(getViewLifecycleOwner(),
+ result -> result.onError(this::handleException)
+ .onSuccess(adapter::submitList)
+ );
return contentView;
}
@@ -104,14 +68,13 @@ public class ContactChooserFragment extends BaseFragment {
@Override
public void onStart() {
super.onStart();
- loadContacts();
+ list.startPeriodicUpdate();
}
@Override
public void onStop() {
super.onStop();
- adapter.clear();
- list.showProgressBar();
+ list.stopPeriodicUpdate();
}
@Override
@@ -119,43 +82,9 @@ public class ContactChooserFragment extends BaseFragment {
return TAG;
}
- private void loadContacts() {
- listener.runOnDbThread(() -> {
- try {
- List contacts = new ArrayList<>();
- for (Contact c : contactManager.getContacts()) {
- if (c.getId().equals(contactId)) {
- c1 = c;
- } else {
- AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
- ContactId id = c.getId();
- GroupCount count =
- conversationManager.getGroupCount(id);
- boolean connected =
- connectionRegistry.isConnected(c.getId());
- contacts.add(new ContactListItem(c, authorInfo,
- connected, count));
- }
- }
- displayContacts(contacts);
- } catch (DbException e) {
- logException(LOG, WARNING, e);
- }
- });
+ @Override
+ public void onItemClick(View view, ContactListItem item) {
+ viewModel.setSecondContactId(item.getContact().getId());
+ viewModel.triggerContactSelected();
}
-
- private void displayContacts(List contacts) {
- runOnUiThreadUnlessDestroyed(() -> {
- if (contacts.isEmpty()) list.showData();
- else adapter.addAll(contacts);
- });
- }
-
- private void showMessageScreen(Contact c1, Contact c2) {
- IntroductionMessageFragment messageFragment =
- IntroductionMessageFragment
- .newInstance(c1.getId().getInt(), c2.getId().getInt());
- showNextFragment(messageFragment);
- }
-
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactListViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/ContactListViewModel.java
new file mode 100644
index 000000000..e69de29bb
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java
index 137d0c585..f2307545d 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionActivity.java
@@ -9,30 +9,67 @@ import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModelProvider;
+
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
public class IntroductionActivity extends BriarActivity
implements BaseFragmentListener {
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private IntroductionViewModel viewModel;
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+ viewModel = new ViewModelProvider(this, viewModelFactory)
+ .get(IntroductionViewModel.class);
+ }
+
+ private static final String BUNDLE_CONTACT2 = "contact2";
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
- int id = intent.getIntExtra(CONTACT_ID, -1);
- if (id == -1) throw new IllegalStateException("No ContactId");
- ContactId contactId = new ContactId(id);
+ int contactId1 = intent.getIntExtra(CONTACT_ID, -1);
+ if (contactId1 == -1)
+ throw new IllegalStateException("No ContactId");
+ ContactId firstContactId = new ContactId(contactId1);
+
+ viewModel.setFirstContactId(firstContactId);
setContentView(R.layout.activity_fragment_container);
if (savedInstanceState == null) {
- showInitialFragment(ContactChooserFragment.newInstance(contactId));
+ showInitialFragment(new ContactChooserFragment());
+ } else {
+ int contactId2 = savedInstanceState.getInt(BUNDLE_CONTACT2);
+ ContactId secondContactId = new ContactId(contactId2);
+ viewModel.setSecondContactId(secondContactId);
}
+
+ viewModel.getSecondContactSelected().observeEvent(this, e -> {
+ IntroductionMessageFragment fragment =
+ new IntroductionMessageFragment();
+ showNextFragment(fragment);
+ });
}
@Override
- public void injectActivity(ActivityComponent component) {
- component.inject(this);
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ ContactId secondContactId = viewModel.getSecondContactId();
+ if (secondContactId != null) {
+ outState.putInt(BUNDLE_CONTACT2, secondContactId.getInt());
+ }
}
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionInfo.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionInfo.java
new file mode 100644
index 000000000..b8061b83d
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionInfo.java
@@ -0,0 +1,28 @@
+package org.briarproject.briar.android.introduction;
+
+import org.briarproject.briar.android.contact.ContactItem;
+
+class IntroductionInfo {
+ private final ContactItem c1;
+ private final ContactItem c2;
+ private final boolean possible;
+
+ IntroductionInfo(ContactItem c1, ContactItem c2,
+ boolean possible) {
+ this.c1 = c1;
+ this.c2 = c2;
+ this.possible = possible;
+ }
+
+ ContactItem getContact1() {
+ return c1;
+ }
+
+ ContactItem getContact2() {
+ return c2;
+ }
+
+ boolean isPossible() {
+ return possible;
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java
index 28ed4bc8b..8c2efe46f 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java
@@ -1,6 +1,5 @@
package org.briarproject.briar.android.introduction;
-import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -8,12 +7,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
-import android.widget.Toast;
-import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.contact.ContactManager;
-import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
@@ -24,25 +18,19 @@ import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.attachment.AttachmentHeader;
-import org.briarproject.briar.api.identity.AuthorInfo;
-import org.briarproject.briar.api.identity.AuthorManager;
-import org.briarproject.briar.api.introduction.IntroductionManager;
import java.util.List;
-import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
-import androidx.appcompat.app.ActionBar;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProvider;
import de.hdodenhof.circleimageview.CircleImageView;
import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
-import static android.widget.Toast.LENGTH_SHORT;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
@@ -53,45 +41,21 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_
public class IntroductionMessageFragment extends BaseFragment
implements SendListener {
- public static final String TAG =
+ private static final String TAG =
IntroductionMessageFragment.class.getName();
- private static final Logger LOG = Logger.getLogger(TAG);
- private final static String CONTACT_ID_1 = "contact1";
- private final static String CONTACT_ID_2 = "contact2";
+ @Inject
+ ViewModelProvider.Factory viewModelFactory;
+
+ private IntroductionViewModel viewModel;
- private IntroductionActivity introductionActivity;
private ViewHolder ui;
- private Contact contact1, contact2;
-
- // Fields that are accessed from background threads must be volatile
- @Inject
- protected volatile ContactManager contactManager;
- @Inject
- protected volatile AuthorManager authorManager;
- @Inject
- protected volatile IntroductionManager introductionManager;
-
- public static IntroductionMessageFragment newInstance(int contactId1,
- int contactId2) {
- Bundle args = new Bundle();
- args.putInt(CONTACT_ID_1, contactId1);
- args.putInt(CONTACT_ID_2, contactId2);
- IntroductionMessageFragment fragment =
- new IntroductionMessageFragment();
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- introductionActivity = (IntroductionActivity) context;
- }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
+ viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
+ .get(IntroductionViewModel.class);
}
@Override
@@ -100,18 +64,7 @@ public class IntroductionMessageFragment extends BaseFragment
@Nullable Bundle savedInstanceState) {
// change toolbar text
- ActionBar actionBar = introductionActivity.getSupportActionBar();
- if (actionBar != null) {
- actionBar.setTitle(R.string.introduction_message_title);
- }
-
- // get contact IDs from fragment arguments
- Bundle args = requireArguments();
- int contactId1 = args.getInt(CONTACT_ID_1, -1);
- int contactId2 = args.getInt(CONTACT_ID_2, -1);
- if (contactId1 == -1 || contactId2 == -1) {
- throw new AssertionError("Use newInstance() to instantiate");
- }
+ requireActivity().setTitle(R.string.introduction_message_title);
// inflate view
View v = inflater.inflate(R.layout.introduction_message, container,
@@ -123,69 +76,44 @@ public class IntroductionMessageFragment extends BaseFragment
ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH);
ui.message.setReady(false);
- // get contacts and then show view
- prepareToSetUpViews(contactId1, contactId2);
+ viewModel.getIntroductionInfo().observe(getViewLifecycleOwner(), ii -> {
+ if (ii == null) {
+ return;
+ }
+ setUpViews(ii.getContact1(), ii.getContact2(),
+ ii.isPossible());
+ });
return v;
}
- @Override
- public void onStart() {
- super.onStart();
- }
-
@Override
public String getUniqueTag() {
return TAG;
}
- private void prepareToSetUpViews(int contactId1, int contactId2) {
- introductionActivity.runOnDbThread(() -> {
- try {
- Contact contact1 =
- contactManager.getContact(new ContactId(contactId1));
- Contact contact2 =
- contactManager.getContact(new ContactId(contactId2));
- AuthorInfo a1 = authorManager.getAuthorInfo(contact1);
- AuthorInfo a2 = authorManager.getAuthorInfo(contact2);
- boolean possible =
- introductionManager.canIntroduce(contact1, contact2);
- ContactItem c1 = new ContactItem(contact1, a1);
- ContactItem c2 = new ContactItem(contact2, a2);
- setUpViews(c1, c2, possible);
- } catch (DbException e) {
- logException(LOG, WARNING, e);
- }
- });
- }
-
private void setUpViews(ContactItem c1, ContactItem c2, boolean possible) {
- introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
- contact1 = c1.getContact();
- contact2 = c2.getContact();
+ // set avatars
+ setAvatar(ui.avatar1, c1);
+ setAvatar(ui.avatar2, c2);
- // set avatars
- setAvatar(ui.avatar1, c1);
- setAvatar(ui.avatar2, c2);
+ // set contact names
+ ui.contactName1.setText(getContactDisplayName(c1.getContact()));
+ ui.contactName2.setText(getContactDisplayName(c2.getContact()));
- // set contact names
- ui.contactName1.setText(getContactDisplayName(c1.getContact()));
- ui.contactName2.setText(getContactDisplayName(c2.getContact()));
+ // hide progress bar
+ ui.progressBar.setVisibility(GONE);
- // hide progress bar
- ui.progressBar.setVisibility(GONE);
-
- if (possible) {
- // show views
- ui.notPossible.setVisibility(GONE);
- ui.message.setVisibility(VISIBLE);
- ui.message.setReady(true);
- ui.message.showSoftKeyboard();
- } else {
- ui.notPossible.setVisibility(VISIBLE);
- ui.message.setVisibility(GONE);
- }
- });
+ if (possible) {
+ // show views
+ ui.notPossible.setVisibility(GONE);
+ ui.message.setVisibility(VISIBLE);
+ ui.message.setReady(true);
+ ui.message.showSoftKeyboard();
+ } else {
+ ui.notPossible.setVisibility(VISIBLE);
+ ui.message.setVisibility(GONE);
+ }
}
@Override
@@ -193,7 +121,7 @@ public class IntroductionMessageFragment extends BaseFragment
switch (item.getItemId()) {
case android.R.id.home:
hideSoftKeyboard(ui.message);
- introductionActivity.onBackPressed();
+ requireActivity().onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
@@ -206,32 +134,13 @@ public class IntroductionMessageFragment extends BaseFragment
// disable button to prevent accidental double invitations
ui.message.setReady(false);
- makeIntroduction(contact1, contact2, text);
+ viewModel.makeIntroduction(text);
// don't wait for the introduction to be made before finishing activity
hideSoftKeyboard(ui.message);
- introductionActivity.setResult(RESULT_OK);
- introductionActivity.supportFinishAfterTransition();
- }
-
- private void makeIntroduction(Contact c1, Contact c2,
- @Nullable String text) {
- introductionActivity.runOnDbThread(() -> {
- // actually make the introduction
- try {
- long timestamp = System.currentTimeMillis();
- introductionManager.makeIntroduction(c1, c2, text, timestamp);
- } catch (DbException e) {
- logException(LOG, WARNING, e);
- introductionError();
- }
- });
- }
-
- private void introductionError() {
- introductionActivity.runOnUiThreadUnlessDestroyed(
- () -> Toast.makeText(introductionActivity,
- R.string.introduction_error, LENGTH_SHORT).show());
+ FragmentActivity activity = requireActivity();
+ activity.setResult(RESULT_OK);
+ activity.supportFinishAfterTransition();
}
private static class ViewHolder {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionModule.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionModule.java
new file mode 100644
index 000000000..7b3678ee7
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionModule.java
@@ -0,0 +1,19 @@
+package org.briarproject.briar.android.introduction;
+
+import org.briarproject.briar.android.viewmodel.ViewModelKey;
+
+import androidx.lifecycle.ViewModel;
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.IntoMap;
+
+@Module
+public abstract class IntroductionModule {
+
+ @Binds
+ @IntoMap
+ @ViewModelKey(IntroductionViewModel.class)
+ abstract ViewModel bindIntroductionViewModel(
+ IntroductionViewModel introductionViewModel);
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java
new file mode 100644
index 000000000..94ea3a4f3
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionViewModel.java
@@ -0,0 +1,181 @@
+package org.briarproject.briar.android.introduction;
+
+import android.app.Application;
+import android.widget.Toast;
+
+import org.briarproject.bramble.api.connection.ConnectionRegistry;
+import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.db.DatabaseExecutor;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.TransactionManager;
+import org.briarproject.bramble.api.event.EventBus;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.system.AndroidExecutor;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.contact.ContactItem;
+import org.briarproject.briar.android.contact.ContactsViewModel;
+import org.briarproject.briar.android.viewmodel.LiveEvent;
+import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
+import org.briarproject.briar.api.conversation.ConversationManager;
+import org.briarproject.briar.api.identity.AuthorInfo;
+import org.briarproject.briar.api.identity.AuthorManager;
+import org.briarproject.briar.api.introduction.IntroductionManager;
+
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import static android.widget.Toast.LENGTH_SHORT;
+import static java.util.Objects.requireNonNull;
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.util.LogUtils.logException;
+
+@NotNullByDefault
+class IntroductionViewModel extends ContactsViewModel {
+
+ private static final Logger LOG =
+ getLogger(IntroductionViewModel.class.getName());
+
+ private final ContactManager contactManager;
+ private final AuthorManager authorManager;
+ private final IntroductionManager introductionManager;
+
+ @Inject
+ IntroductionViewModel(Application application,
+ @DatabaseExecutor Executor dbExecutor,
+ LifecycleManager lifecycleManager, TransactionManager db,
+ AndroidExecutor androidExecutor, ContactManager contactManager,
+ AuthorManager authorManager,
+ ConversationManager conversationManager,
+ ConnectionRegistry connectionRegistry, EventBus eventBus,
+ IntroductionManager introductionManager) {
+ super(application, dbExecutor, lifecycleManager, db, androidExecutor,
+ contactManager, authorManager, conversationManager,
+ connectionRegistry, eventBus);
+ this.contactManager = contactManager;
+ this.authorManager = authorManager;
+ this.introductionManager = introductionManager;
+ }
+
+ /*
+ * This is the contact from whose conversation we started the introduction
+ * using the menu item.
+ */
+ @Nullable
+ private ContactId firstContactId;
+ /*
+ * This is the contact we selected from the list of contacts as a second
+ * contact for the introduction.
+ */
+ @Nullable
+ private ContactId secondContactId;
+
+ private final MutableLiveEvent secondContactSelected =
+ new MutableLiveEvent<>();
+
+ private final MutableLiveData introductionInfo =
+ new MutableLiveData<>();
+
+ void setFirstContactId(ContactId contactId) {
+ this.firstContactId = contactId;
+ loadContacts();
+ }
+
+ @Nullable
+ ContactId getSecondContactId() {
+ return secondContactId;
+ }
+
+ void setSecondContactId(ContactId contactId) {
+ secondContactId = contactId;
+ // Setting this to null here so that IntroductionMessageFragment can
+ // tell whether the correct value has been loaded from the database when
+ // selecting a second contact repeatedly.
+ introductionInfo.setValue(null);
+ loadIntroductionInfo();
+ }
+
+ /**
+ * Trigger the event that the second contact has been selected from the
+ * contact list by the user.
+ */
+ void triggerContactSelected() {
+ secondContactSelected.setEvent(true);
+ }
+
+ /**
+ * This event will be triggered once the second contact has been selected
+ * from the list of contacts displayed. It is not fired when the second
+ * contact gets restored from the saved instance state.
+ */
+ LiveEvent getSecondContactSelected() {
+ return secondContactSelected;
+ }
+
+ /**
+ * Holder for the introduction info object with data about both contacts
+ * and whether the introduction is possible. May wrap null if the data
+ * is not available yet. This happens when it is reset by selecting a
+ * contact with the same view model instance more than once.
+ */
+ LiveData getIntroductionInfo() {
+ return introductionInfo;
+ }
+
+ @Override
+ protected boolean displayContact(ContactId contactId) {
+ return !requireNonNull(firstContactId).equals(contactId);
+ }
+
+ private void loadIntroductionInfo() {
+ final ContactId firstContactId = requireNonNull(this.firstContactId);
+ final ContactId secondContactId = requireNonNull(this.secondContactId);
+ runOnDbThread(() -> {
+ try {
+ Contact firstContact =
+ contactManager.getContact(firstContactId);
+ Contact secondContact =
+ contactManager.getContact(secondContactId);
+ AuthorInfo a1 = authorManager.getAuthorInfo(firstContact);
+ AuthorInfo a2 = authorManager.getAuthorInfo(secondContact);
+ boolean possible = introductionManager
+ .canIntroduce(firstContact, secondContact);
+ ContactItem c1 = new ContactItem(firstContact, a1);
+ ContactItem c2 = new ContactItem(secondContact, a2);
+ introductionInfo.postValue(
+ new IntroductionInfo(c1, c2, possible));
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ }
+ });
+ }
+
+ void makeIntroduction(@Nullable String text) {
+ final IntroductionInfo info =
+ requireNonNull(introductionInfo.getValue());
+ runOnDbThread(() -> {
+ // actually make the introduction
+ try {
+ long timestamp = System.currentTimeMillis();
+ introductionManager.makeIntroduction(
+ info.getContact1().getContact(),
+ info.getContact2().getContact(), text, timestamp);
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ androidExecutor.runOnUiThread(() -> Toast.makeText(
+ getApplication(), R.string.introduction_error,
+ LENGTH_SHORT).show());
+ }
+ });
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java
deleted file mode 100644
index 222fba77b..000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package org.briarproject.briar.android.keyagreement;
-
-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.activity.ActivityComponent;
-
-import javax.annotation.Nullable;
-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;
-
-@MethodsNotNullByDefault
-@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 = ViewModelProviders.of(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);
- }
- });
- viewModel.startContactExchange(result.getTransportId(),
- result.getConnection(), result.getMasterKey(),
- result.wasAlice());
- }
-
- @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 showErrorFragment() {
- showNextFragment(new ContactExchangeErrorFragment());
- }
-}
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/keyagreement/ContactExchangeViewModel.java
deleted file mode 100644
index 054121cac..000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.briarproject.briar.android.keyagreement;
-
-import android.app.Application;
-
-import org.briarproject.bramble.api.connection.ConnectionManager;
-import org.briarproject.bramble.api.contact.Contact;
-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 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;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-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;
-
-@NotNullByDefault
-class ContactExchangeViewModel extends AndroidViewModel {
-
- private static final Logger LOG =
- getLogger(ContactExchangeViewModel.class.getName());
-
- private final Executor ioExecutor;
- private final ContactExchangeManager contactExchangeManager;
- private final ConnectionManager connectionManager;
- private final MutableLiveData succeeded = new MutableLiveData<>();
-
- @Nullable
- private volatile Author remoteAuthor, duplicateAuthor;
-
- @Inject
- ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor,
- ContactExchangeManager contactExchangeManager,
- ConnectionManager connectionManager) {
- super(app);
- this.ioExecutor = ioExecutor;
- this.contactExchangeManager = contactExchangeManager;
- this.connectionManager = connectionManager;
- }
-
- @UiThread
- void startContactExchange(TransportId t, DuplexTransportConnection conn,
- SecretKey masterKey, boolean alice) {
- ioExecutor.execute(() -> {
- try {
- 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);
- } catch (ContactExistsException e) {
- tryToClose(conn);
- duplicateAuthor = e.getRemoteAuthor();
- succeeded.postValue(false);
- } catch (DbException | IOException e) {
- tryToClose(conn);
- logException(LOG, WARNING, e);
- succeeded.postValue(false);
- }
- });
- }
-
- private void tryToClose(DuplexTransportConnection conn) {
- try {
- conn.getReader().dispose(true, true);
- conn.getWriter().dispose(true);
- } catch (IOException e) {
- logException(LOG, WARNING, e);
- }
- }
-
- @UiThread
- @Nullable
- Author getRemoteAuthor() {
- return remoteAuthor;
- }
-
- @UiThread
- @Nullable
- Author getDuplicateAuthor() {
- return duplicateAuthor;
- }
-
- LiveData getSucceeded() {
- return succeeded;
- }
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java
deleted file mode 100644
index a2795b2a0..000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java
+++ /dev/null
@@ -1,482 +0,0 @@
-package org.briarproject.briar.android.keyagreement;
-
-import android.bluetooth.BluetoothAdapter;
-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 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.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 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 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.util.UiUtils.getGoToSettingsListener;
-
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-public abstract class KeyAgreementActivity extends BriarActivity implements
- BaseFragmentListener, IntroScreenSeenListener,
- KeyAgreementEventListener, 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
- }
-
- private enum Permission {
- UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
- }
-
- private static final Logger LOG =
- getLogger(KeyAgreementActivity.class.getName());
-
- @Inject
- EventBus eventBus;
-
- @Inject
- PluginManager pluginManager;
-
- /**
- * 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;
-
- /**
- * 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) {
- component.inject(this);
- }
-
- @Override
- public void onCreate(@Nullable Bundle state) {
- super.onCreate(state);
- setContentView(R.layout.activity_fragment_container_toolbar);
- Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
- requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
- if (state == null) {
- showInitialFragment(IntroFragment.newInstance());
- }
- 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();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- 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 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() {
- if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
- if (isWifiReady() && isBluetoothReady()) {
- LOG.info("Wifi and Bluetooth are ready");
- showQrCodeFragment();
- } else {
- if (shouldEnableWifi()) {
- LOG.info("Enabling wifi plugin");
- hasEnabledWifi = true;
- pluginManager.setPluginEnabled(LanTcpConstants.ID, true);
- }
- if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
- requestBluetoothDiscoverable();
- } else if (bluetoothDecision == BluetoothDecision.REFUSED) {
- // Ask again when the user clicks "continue"
- } else if (shouldEnableBluetooth()) {
- LOG.info("Enabling Bluetooth plugin");
- hasEnabledBluetooth = true;
- pluginManager.setPluginEnabled(BluetoothConstants.ID, true);
- }
- }
- }
- }
-
- 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;
- }
-
- private void requestBluetoothDiscoverable() {
- if (!isBluetoothSupported()) {
- 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;
- startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
- } else {
- 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;
- } else {
- LOG.info("Bluetooth discoverability was accepted");
- bluetoothDecision = 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) {
- BaseFragment f = KeyAgreementFragment.newInstance();
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, f, f.getUniqueTag())
- .addToBackStack(f.getUniqueTag())
- .commit();
- }
- }
-
- 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 (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 (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) {
- 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()) 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);
- }
-
- @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
- 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/keyagreement/KeyAgreementFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java
deleted file mode 100644
index eb8f1bced..000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java
+++ /dev/null
@@ -1,375 +0,0 @@
-package org.briarproject.briar.android.keyagreement;
-
-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;
-import android.widget.LinearLayout;
-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.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.fragment.BaseEventFragment;
-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 static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-import static android.view.View.INVISIBLE;
-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;
-
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-public class KeyAgreementFragment extends BaseEventFragment
- implements QrCodeDecoder.ResultCallback, 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
- Provider keyAgreementTaskProvider;
- @Inject
- PayloadEncoder payloadEncoder;
- @Inject
- PayloadParser payloadParser;
- @Inject
- @IoExecutor
- Executor ioExecutor;
- @Inject
- EventBus eventBus;
-
- private CameraView cameraView;
- private LinearLayout cameraOverlay;
- private View statusView;
- private QrCodeView qrCodeView;
- private TextView status;
-
- private boolean gotRemotePayload;
- private volatile boolean gotLocalPayload;
- private KeyAgreementTask task;
- private KeyAgreementEventListener listener;
-
- public static KeyAgreementFragment newInstance() {
- Bundle args = new Bundle();
- KeyAgreementFragment fragment = new KeyAgreementFragment();
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- listener = (KeyAgreementEventListener) context;
- }
-
- @Override
- public void injectFragment(ActivityComponent component) {
- component.inject(this);
- }
-
- @Override
- public String getUniqueTag() {
- return TAG;
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_keyagreement_qr, container,
- false);
- }
-
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- cameraView = view.findViewById(R.id.camera_view);
- 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);
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
- cameraView.setPreviewConsumer(new QrCodeDecoder(this));
- }
-
- @Override
- public void onStart() {
- super.onStart();
- try {
- cameraView.start();
- } catch (CameraException e) {
- logCameraExceptionAndFinish(e);
- }
- startListening();
- }
-
- @Override
- public void setFullscreen(boolean fullscreen) {
- LinearLayout.LayoutParams statusParams, qrCodeParams;
- if (fullscreen) {
- // Grow the QR code view to fill its parent
- statusParams = new LayoutParams(0, 0, 0f);
- qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
- } else {
- // Shrink the QR code view to fill half its parent
- if (cameraOverlay.getOrientation() == HORIZONTAL) {
- statusParams = new LayoutParams(0, MATCH_PARENT, 1f);
- qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f);
- } else {
- statusParams = new LayoutParams(MATCH_PARENT, 0, 1f);
- qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f);
- }
- }
- statusView.setLayoutParams(statusParams);
- qrCodeView.setLayoutParams(qrCodeParams);
- cameraOverlay.invalidate();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- stopListening();
- try {
- cameraView.stop();
- } catch (CameraException e) {
- logCameraExceptionAndFinish(e);
- }
- }
-
- @UiThread
- private void logCameraExceptionAndFinish(CameraException e) {
- logException(LOG, WARNING, e);
- Toast.makeText(getActivity(), R.string.camera_error,
- LENGTH_LONG).show();
- 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());
- } 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 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() {
- getActivity().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);
- }
-}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
index ced01d62e..c16fcd4e1 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
@@ -49,14 +49,12 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -67,9 +65,7 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.core.view.GravityCompat.START;
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
-import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static androidx.lifecycle.Lifecycle.State.STARTED;
-import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@@ -146,7 +142,7 @@ public class NavDrawerActivity extends BriarActivity implements
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
});
- Toolbar toolbar = findViewById(R.id.toolbar);
+ Toolbar toolbar = setUpCustomToolbar(false);
drawerLayout = findViewById(R.id.drawer_layout);
navigation = findViewById(R.id.navigation);
GridView transportsView = findViewById(R.id.transportsView);
@@ -156,11 +152,6 @@ public class NavDrawerActivity extends BriarActivity implements
startActivity(new Intent(this, TransportsActivity.class));
});
- setSupportActionBar(toolbar);
- ActionBar actionBar = requireNonNull(getSupportActionBar());
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setHomeButtonEnabled(true);
-
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
R.string.nav_drawer_open_description,
R.string.nav_drawer_close_description) {
@@ -184,9 +175,6 @@ public class NavDrawerActivity extends BriarActivity implements
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
showSignOutFragment();
- } else if (state == null) {
- startFragment(ContactListFragment.newInstance(),
- R.id.nav_btn_contacts);
}
if (state == null) {
// do not call this again when there's existing state
@@ -276,7 +264,6 @@ public class NavDrawerActivity extends BriarActivity implements
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawer(START);
- clearBackStack();
if (item.getItemId() == R.id.nav_btn_lock) {
lockManager.setLocked(true);
ActivityCompat.finishAfterTransition(this);
@@ -296,8 +283,8 @@ public class NavDrawerActivity extends BriarActivity implements
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
finish();
- } else if (fm.getBackStackEntryCount() == 0
- && fm.findFragmentByTag(ContactListFragment.TAG) == null) {
+ } else if (fm.getBackStackEntryCount() == 0 &&
+ fm.findFragmentByTag(ContactListFragment.TAG) == null) {
// don't start fragments in the wrong part of lifecycle (#1904)
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
LOG.warning("Tried to start contacts fragment in state " +
@@ -346,30 +333,12 @@ public class NavDrawerActivity extends BriarActivity implements
startFragment(fragment);
}
- private void startFragment(BaseFragment fragment) {
- if (getSupportFragmentManager().getBackStackEntryCount() == 0)
- startFragment(fragment, false);
- else startFragment(fragment, true);
- }
-
- private void startFragment(BaseFragment fragment,
- boolean isAddedToBackStack) {
- FragmentTransaction trans =
- getSupportFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.fade_in,
- R.anim.fade_out, R.anim.fade_in,
- R.anim.fade_out)
- .replace(R.id.fragmentContainer, fragment,
- fragment.getUniqueTag());
- if (isAddedToBackStack) {
- trans.addToBackStack(fragment.getUniqueTag());
- }
- trans.commit();
- }
-
- private void clearBackStack() {
- getSupportFragmentManager().popBackStackImmediate(null,
- POP_BACK_STACK_INCLUSIVE);
+ private void startFragment(BaseFragment f) {
+ getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(R.anim.fade_in, R.anim.fade_out,
+ R.anim.fade_in, R.anim.fade_out)
+ .replace(R.id.fragmentContainer, f, f.getUniqueTag())
+ .commit();
}
@Override
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java
index f2648ed12..4cf47c5f2 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealContactsFragment.java
@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.activity.ActivityComponent;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.briar.android.contactselection.BaseContactSelectorFragment;
import org.briarproject.briar.android.contactselection.ContactSelectorController;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java
index 1ac55740e..271b97ebb 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactAdapter.java
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.briar.android.contactselection.BaseContactSelectorAdapter;
import java.util.ArrayList;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java
index 87edf059c..02bfa90a9 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/reveal/RevealableContactViewHolder.java
@@ -5,7 +5,7 @@ import android.widget.ImageView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
-import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
+import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.briar.android.contactselection.BaseSelectableContactHolder;
import javax.annotation.Nullable;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConfirmAvatarDialogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConfirmAvatarDialogFragment.java
index 4c87a3fc0..c3d200aa4 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConfirmAvatarDialogFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConfirmAvatarDialogFragment.java
@@ -9,10 +9,13 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+
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.BaseActivity;
+import org.briarproject.briar.android.conversation.glide.GlideApp;
import javax.inject.Inject;
@@ -32,7 +35,7 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
@Inject
ViewModelProvider.Factory viewModelFactory;
- private SettingsViewModel settingsViewModel;
+ private SettingsViewModel viewModel;
private static final String ARG_URI = "uri";
private Uri uri;
@@ -51,6 +54,9 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
public void onAttach(Context ctx) {
super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
+ ViewModelProvider provider =
+ new ViewModelProvider(requireActivity(), viewModelFactory);
+ viewModel = provider.get(SettingsViewModel.class);
}
@Override
@@ -60,32 +66,34 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
uri = Uri.parse(argUri);
FragmentActivity activity = requireActivity();
-
- ViewModelProvider provider =
- new ViewModelProvider(activity, viewModelFactory);
- settingsViewModel = provider.get(SettingsViewModel.class);
-
- AlertDialog.Builder builder =
- new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
-
- LayoutInflater inflater = LayoutInflater.from(getContext());
+ LayoutInflater inflater = LayoutInflater.from(activity);
final View view =
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
- builder.setView(view);
-
- builder.setTitle(R.string.dialog_confirm_profile_picture_title);
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.change,
- (dialog, id) -> settingsViewModel.setAvatar(uri));
-
ImageView imageView = view.findViewById(R.id.image);
- imageView.setImageURI(uri);
-
TextView textViewUserName = view.findViewById(R.id.username);
- settingsViewModel.getOwnIdentityInfo().observe(activity,
- us -> textViewUserName.setText(us.getLocalAuthor().getName()));
- return builder.create();
+ GlideApp.with(imageView)
+ .load(uri)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .error(R.drawable.ic_image_broken)
+ .into(imageView)
+ .waitForLayout();
+
+ // we can't use getViewLifecycleOwner() here
+ // as this fragment technically doesn't have a view
+ viewModel.getOwnIdentityInfo().observe(activity, us ->
+ textViewUserName.setText(us.getLocalAuthor().getName())
+ );
+
+ int theme = R.style.BriarDialogTheme;
+ return new AlertDialog.Builder(activity, theme)
+ .setView(view)
+ .setTitle(R.string.dialog_confirm_profile_picture_title)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.change, (d, id) ->
+ viewModel.setAvatar(uri)
+ )
+ .create();
}
}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java
index c6821b92a..803e2bbf2 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java
@@ -79,7 +79,7 @@ class SettingsViewModel extends AndroidViewModel {
return ownIdentityInfo;
}
- public LiveEvent getSetAvatarFailed() {
+ LiveEvent getSetAvatarFailed() {
return setAvatarFailed;
}
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/java/org/briarproject/briar/android/view/BriarRecyclerView.java b/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java
index c54b3ea17..9e01848fe 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/view/BriarRecyclerView.java
@@ -52,7 +52,8 @@ public class BriarRecyclerView extends FrameLayout {
R.styleable.BriarRecyclerView);
isScrollingToEnd = attributes
.getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true);
- int drawableRes = attributes.getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1);
+ int drawableRes = attributes
+ .getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1);
if (drawableRes != -1) setEmptyImage(drawableRes);
String emtpyText =
attributes.getString(R.styleable.BriarRecyclerView_emptyText);
@@ -87,10 +88,30 @@ public class BriarRecyclerView extends FrameLayout {
}
emptyObserver = new RecyclerView.AdapterDataObserver() {
+
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ showData();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ super.onItemRangeChanged(positionStart, itemCount);
+ if (itemCount > 0) showData();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition,
+ int itemCount) {
+ super.onItemRangeMoved(fromPosition, toPosition, itemCount);
+ if (itemCount > 0) showData();
+ }
+
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
- if (itemCount > 0) showData();
+ showData();
}
@Override
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java
index e237b3962..7d3875723 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/DbViewModel.java
@@ -43,7 +43,7 @@ public abstract class DbViewModel extends AndroidViewModel {
private final Executor dbExecutor;
private final LifecycleManager lifecycleManager;
private final TransactionManager db;
- private final AndroidExecutor androidExecutor;
+ protected final AndroidExecutor androidExecutor;
public DbViewModel(
@NonNull Application application,
diff --git a/briar-android/src/main/res/anim/fade_in.xml b/briar-android/src/main/res/anim/fade_in.xml
index 4d3bef4a3..83abd6b5d 100644
--- a/briar-android/src/main/res/anim/fade_in.xml
+++ b/briar-android/src/main/res/anim/fade_in.xml
@@ -1,7 +1,6 @@
-
+ android:toAlpha="1.0" />
diff --git a/briar-android/src/main/res/anim/fade_out.xml b/briar-android/src/main/res/anim/fade_out.xml
index 84bad946f..205435cca 100644
--- a/briar-android/src/main/res/anim/fade_out.xml
+++ b/briar-android/src/main/res/anim/fade_out.xml
@@ -1,7 +1,6 @@
-
+ android:toAlpha="0.0" />
diff --git a/briar-android/src/main/res/anim/screen_new_in.xml b/briar-android/src/main/res/anim/screen_new_in.xml
index 6bdf53b16..32b84f2da 100644
--- a/briar-android/src/main/res/anim/screen_new_in.xml
+++ b/briar-android/src/main/res/anim/screen_new_in.xml
@@ -1,12 +1,11 @@
-
+
+ android:toXDelta="0" />
diff --git a/briar-android/src/main/res/anim/screen_new_out.xml b/briar-android/src/main/res/anim/screen_new_out.xml
index 5411fa11b..ee1b18e39 100644
--- a/briar-android/src/main/res/anim/screen_new_out.xml
+++ b/briar-android/src/main/res/anim/screen_new_out.xml
@@ -1,12 +1,11 @@
-
+
+ android:toXDelta="100%p" />
diff --git a/briar-android/src/main/res/anim/screen_old_in.xml b/briar-android/src/main/res/anim/screen_old_in.xml
index 2360d85b5..83abd6b5d 100644
--- a/briar-android/src/main/res/anim/screen_old_in.xml
+++ b/briar-android/src/main/res/anim/screen_old_in.xml
@@ -1,7 +1,6 @@
-
+ android:toAlpha="1.0" />
diff --git a/briar-android/src/main/res/anim/screen_old_out.xml b/briar-android/src/main/res/anim/screen_old_out.xml
index 84bad946f..205435cca 100644
--- a/briar-android/src/main/res/anim/screen_old_out.xml
+++ b/briar-android/src/main/res/anim/screen_old_out.xml
@@ -1,7 +1,6 @@
-
+ android:toAlpha="0.0" />
diff --git a/briar-android/src/main/res/anim/step_next_in.xml b/briar-android/src/main/res/anim/step_next_in.xml
index 6bdf53b16..77549112b 100644
--- a/briar-android/src/main/res/anim/step_next_in.xml
+++ b/briar-android/src/main/res/anim/step_next_in.xml
@@ -4,7 +4,7 @@
diff --git a/briar-android/src/main/res/anim/step_next_out.xml b/briar-android/src/main/res/anim/step_next_out.xml
index 5411fa11b..ee1b18e39 100644
--- a/briar-android/src/main/res/anim/step_next_out.xml
+++ b/briar-android/src/main/res/anim/step_next_out.xml
@@ -1,12 +1,11 @@
-
+
+ android:toXDelta="100%p" />
diff --git a/briar-android/src/main/res/anim/step_previous_in.xml b/briar-android/src/main/res/anim/step_previous_in.xml
index 2360d85b5..83abd6b5d 100644
--- a/briar-android/src/main/res/anim/step_previous_in.xml
+++ b/briar-android/src/main/res/anim/step_previous_in.xml
@@ -1,7 +1,6 @@
-
+ android:toAlpha="1.0" />
diff --git a/briar-android/src/main/res/anim/step_previous_out.xml b/briar-android/src/main/res/anim/step_previous_out.xml
index 84bad946f..205435cca 100644
--- a/briar-android/src/main/res/anim/step_previous_out.xml
+++ b/briar-android/src/main/res/anim/step_previous_out.xml
@@ -1,7 +1,6 @@
-
+ android:toAlpha="0.0" />
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..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,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.AddNearbyContactActivity">
-
diff --git a/briar-android/src/main/res/layout/activity_nav_drawer.xml b/briar-android/src/main/res/layout/activity_nav_drawer.xml
index 44830d9c5..c9e1f38ca 100644
--- a/briar-android/src/main/res/layout/activity_nav_drawer.xml
+++ b/briar-android/src/main/res/layout/activity_nav_drawer.xml
@@ -52,16 +52,17 @@
android:layout_height="0dp"
android:contentDescription="@string/close"
android:scaleType="center"
- app:srcCompat="@drawable/ic_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
+ app:srcCompat="@drawable/ic_close"
app:tint="@color/briar_text_tertiary_inverse" />
-
-
diff --git a/briar-android/src/main/res/menu/navigation_drawer.xml b/briar-android/src/main/res/menu/navigation_drawer.xml
index ccfa71328..6ec0515e4 100644
--- a/briar-android/src/main/res/menu/navigation_drawer.xml
+++ b/briar-android/src/main/res/menu/navigation_drawer.xml
@@ -5,27 +5,28 @@
+ android:title="@string/contact_list_button" />
+ android:title="@string/groups_button" />
+ android:title="@string/forums_button" />
+ android:title="@string/blogs_button" />
+ android:title="@string/settings_button" />
+ android:title="@string/sign_out_button" />
\ No newline at end of file
diff --git a/briar-android/src/main/res/values/attrs.xml b/briar-android/src/main/res/values/attrs.xml
index 588dc4b8d..51e0b887f 100644
--- a/briar-android/src/main/res/values/attrs.xml
+++ b/briar-android/src/main/res/values/attrs.xml
@@ -1,6 +1,8 @@
+ @android:integer/config_mediumAnimTime
+
diff --git a/briar-android/src/main/res/xml/backup_rules.xml b/briar-android/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..200b2d3cd
--- /dev/null
+++ b/briar-android/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/witness.gradle b/briar-android/witness.gradle
index a44d295d8..3bef77ee0 100644
--- a/briar-android/witness.gradle
+++ b/briar-android/witness.gradle
@@ -1,7 +1,7 @@
dependencyVerification {
verify = [
'androidx.activity:activity-ktx:1.1.0:activity-ktx-1.1.0.aar:1996c36d3d2d62db5020b8ec634b5f854b1a698960c3552e1a00c69221baeabe',
- 'androidx.activity:activity:1.1.0:activity-1.1.0.aar:4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef',
+ 'androidx.activity:activity:1.2.0:activity-1.2.0.aar:ac27a810554e47b2122bce1f338934e77b173a5a9267eb35f134b6d34f931bae',
'androidx.annotation:annotation-experimental:1.0.0:annotation-experimental-1.0.0.aar:b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11',
'androidx.annotation:annotation:1.1.0:annotation-1.1.0.jar:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
'androidx.appcompat:appcompat-resources:1.2.0:appcompat-resources-1.2.0.aar:c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5',
@@ -24,29 +24,28 @@ dependencyVerification {
'androidx.exifinterface:exifinterface:1.3.1:exifinterface-1.3.1.aar:ef168daa6eb744c8395c22b49afa5235e6099868a0377175b6d5e3cdff8d7ffc',
'androidx.fragment:fragment-ktx:1.2.5:fragment-ktx-1.2.5.aar:50f0f3b734f93829eeac7456b7cb13e5430741e555c535911a958ee4a8242bca',
'androidx.fragment:fragment-testing:1.2.5:fragment-testing-1.2.5.aar:ef3cc3387115f9187665b283e313b13a2bb8826673380317057e2972351df09c',
- 'androidx.fragment:fragment:1.2.4:fragment-1.2.4.aar:1dc194942574302bf35dae7b81b82273505ec2d38f81d9258ad5c0448daddd82',
- 'androidx.fragment:fragment:1.2.5:fragment-1.2.5.aar:d19e82d142def6c4e136da70bf92f194c0ecc61d14ab4e84567b2ced0920fa93',
+ 'androidx.fragment:fragment:1.3.0:fragment-1.3.0.aar:66db3ed2b11bb5e572a079b87cd3fae9bc5c33c373c71b25f1e3eac7607ab526',
'androidx.interpolator:interpolator:1.0.0:interpolator-1.0.0.aar:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
'androidx.legacy:legacy-support-core-utils:1.0.0:legacy-support-core-utils-1.0.0.aar:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
- 'androidx.lifecycle:lifecycle-common:2.2.0:lifecycle-common-2.2.0.jar:63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b',
+ 'androidx.lifecycle:lifecycle-common:2.3.0:lifecycle-common-2.3.0.jar:15848fb56db32f4c7cdc72b324003183d52a4884d6bf09be708ac7f587d139b5',
'androidx.lifecycle:lifecycle-extensions:2.2.0:lifecycle-extensions-2.2.0.aar:648c8de1d10b025d524a2e46ac994fc3f6bf186826c09ec1a62d250bf1b877ae',
'androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0:lifecycle-livedata-core-ktx-2.2.0.aar:5951f882e95b7e05ceb9adfca0fa2ebd511d63ea5a00da4eae6c6d0c1903da18',
- 'androidx.lifecycle:lifecycle-livedata-core:2.2.0:lifecycle-livedata-core-2.2.0.aar:556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614',
+ 'androidx.lifecycle:lifecycle-livedata-core:2.3.0:lifecycle-livedata-core-2.3.0.aar:89f480888f2bb8eb62d9b7b1eb34be69b59ec84b24a1b0bdbeb49973478c6da3',
'androidx.lifecycle:lifecycle-livedata:2.2.0:lifecycle-livedata-2.2.0.aar:d83af94860aa9f64cbdc51f40796a7cf55b116f0e6efd752e845c0104c8b16f6',
'androidx.lifecycle:lifecycle-process:2.2.0:lifecycle-process-2.2.0.aar:3a977e7778fc8418742d388409daaba7ea8fea8823d21ffb96e4c4236f715070',
'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0:lifecycle-runtime-ktx-2.2.0.aar:c29fc87694e6ce116b61207221e53ed285862a6628055790b0bcf9ce45d8cc68',
- 'androidx.lifecycle:lifecycle-runtime:2.2.0:lifecycle-runtime-2.2.0.aar:2f866c07a1f33a8c9bb69a9545d4f20b4f0628cd0a155432386d7cb081e1e0bc',
+ 'androidx.lifecycle:lifecycle-runtime:2.3.0:lifecycle-runtime-2.3.0.aar:94f528fd5fb123f75b6e65d07a6ef5cd6c0e69ac604d106aaa12705282456234',
'androidx.lifecycle:lifecycle-service:2.2.0:lifecycle-service-2.2.0.aar:ca2801ffc069555afed8eddd2292130f436956452bc8bbad30fb56f8e4e382a0',
'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0:lifecycle-viewmodel-ktx-2.2.0.aar:f791001f2211947e56ad3d96d12c9ae93fc5589b88f08603f69a2265c9a7d702',
- 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0:lifecycle-viewmodel-savedstate-2.2.0.aar:3ce866fb822b20fe2f188f974992869a0a6233fe40acbefcff090d6def5e7f33',
- 'androidx.lifecycle:lifecycle-viewmodel:2.2.0:lifecycle-viewmodel-2.2.0.aar:967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27',
+ 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0:lifecycle-viewmodel-savedstate-2.3.0.aar:49f9532b5104cc1ee64900ed4f696d031d807fba726e0d5d6a52459e8fba4a1d',
+ 'androidx.lifecycle:lifecycle-viewmodel:2.3.0:lifecycle-viewmodel-2.3.0.aar:cea8f26fa232037922b69af9cd1bde2df1211acc8b75253e425b7150a5fca59d',
'androidx.loader:loader:1.0.0:loader-1.0.0.aar:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
'androidx.preference:preference:1.1.1:preference-1.1.1.aar:317dcbc38242aea2f6262c06d51b8a22827e98959967edd40f82600a15cb4bff',
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
'androidx.recyclerview:recyclerview-selection:1.1.0-rc03:recyclerview-selection-1.1.0-rc03.aar:a548a0771c2c8ca8cf98f1f755b0eef4fac73d1697e6eeb1a6383f557e0eba13',
'androidx.recyclerview:recyclerview:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f',
- 'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
+ 'androidx.savedstate:savedstate:1.1.0:savedstate-1.1.0.aar:d60bbe44c2c08083a17c5dc678a6d6b4d0a2d664858016ab5c049cbea90a63b7',
'androidx.test.espresso:espresso-contrib:3.3.0:espresso-contrib-3.3.0.aar:f400cabdc181356acf6b210e4509dcb9649d9e2b6b6e218c60fcfc15e8a756d1',
'androidx.test.espresso:espresso-core:3.3.0:espresso-core-3.3.0.aar:23ebf6014645e0c60aec7d1ed924d4d4c848ae8c3673b7d8d06b2ec6a56cafee',
'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6',
@@ -57,6 +56,7 @@ dependencyVerification {
'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e',
'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a',
'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b',
+ 'androidx.tracing:tracing:1.0.0:tracing-1.0.0.aar:07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612',
'androidx.transition:transition:1.2.0:transition-1.2.0.aar:a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e',
'androidx.vectordrawable:vectordrawable-animated:1.1.0:vectordrawable-animated-1.1.0.aar:76da2c502371d9c38054df5e2b248d00da87809ed058f3363eae87ce5e2403f8',
'androidx.vectordrawable:vectordrawable:1.1.0:vectordrawable-1.1.0.aar:46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26',
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/attachment/MediaConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/attachment/MediaConstants.java
index 7c13575c3..90c851630 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/attachment/MediaConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/attachment/MediaConstants.java
@@ -11,7 +11,7 @@ public interface MediaConstants {
/**
* The maximum length of an attachment's content type in UTF-8 bytes.
*/
- int MAX_CONTENT_TYPE_BYTES = 50;
+ int MAX_CONTENT_TYPE_BYTES = 80;
/**
* The maximum allowed size of image attachments.
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingConstants.java
index 4a87775e0..c5594fa3f 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingConstants.java
@@ -7,7 +7,17 @@ public interface MessagingConstants {
/**
* The maximum length of a private message's text in UTF-8 bytes.
*/
- int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
+ int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 2048;
+
+ /**
+ * The maximum length of an incoming private message's text in UTF-8 bytes.
+ * This is higher than MAX_PRIVATE_MESSAGE_TEXT_LENGTH for compatibility
+ * with older peers.
+ *
+ * TODO: Remove after a reasonable migration period (added 2021-03-12).
+ */
+ int MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH =
+ MAX_MESSAGE_BODY_LENGTH - 1024;
/**
* The maximum number of attachments per private message.
diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/PrivateMessageValidator.java b/briar-core/src/main/java/org/briarproject/briar/messaging/PrivateMessageValidator.java
index af735c73c..8a1541277 100644
--- a/briar-core/src/main/java/org/briarproject/briar/messaging/PrivateMessageValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/messaging/PrivateMessageValidator.java
@@ -32,7 +32,7 @@ import static org.briarproject.briar.api.attachment.MediaConstants.MAX_CONTENT_T
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_DESCRIPTOR_LENGTH;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
-import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
+import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
@@ -103,7 +103,7 @@ class PrivateMessageValidator implements MessageValidator {
// Private message text
checkSize(body, 1);
String text = body.getString(0);
- checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
+ checkLength(text, 0, MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_TIMESTAMP, m.getTimestamp());
@@ -117,7 +117,7 @@ class PrivateMessageValidator implements MessageValidator {
// Message type, optional private message text, attachment headers
checkSize(body, 3);
String text = body.getOptionalString(1);
- checkLength(text, 0, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
+ checkLength(text, 0, MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH);
BdfList headers = body.getList(2);
if (text == null) checkSize(headers, 1, MAX_ATTACHMENTS_PER_MESSAGE);
else checkSize(headers, 0, MAX_ATTACHMENTS_PER_MESSAGE);
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
index 8184cc168..07c52a621 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
@@ -1,10 +1,14 @@
package org.briarproject.briar.messaging;
import org.briarproject.bramble.api.UniqueId;
+import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.forum.ForumPost;
@@ -14,6 +18,8 @@ import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.test.BriarTestCase;
import org.junit.Test;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
@@ -22,12 +28,16 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_CONTENT_TYPE_BYTES;
+import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
+import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
import static org.junit.Assert.assertTrue;
public class MessageSizeIntegrationTest extends BriarTestCase {
@@ -40,6 +50,10 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
PrivateMessageFactory privateMessageFactory;
@Inject
ForumPostFactory forumPostFactory;
+ @Inject
+ ClientHelper clientHelper;
+ @Inject
+ MessageFactory messageFactory;
public MessageSizeIntegrationTest() {
MessageSizeIntegrationTestComponent component =
@@ -87,6 +101,32 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
}
+ @Test
+ public void testAttachmentFitsIntoRecord() throws Exception {
+ // Create a maximum-length attachment
+ String contentType = getRandomString(MAX_CONTENT_TYPE_BYTES);
+ byte[] data = getRandomBytes(MAX_IMAGE_SIZE);
+
+ ByteArrayInputStream dataIn = new ByteArrayInputStream(data);
+ ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
+ byte[] descriptor =
+ clientHelper.toByteArray(BdfList.of(ATTACHMENT, contentType));
+ bodyOut.write(descriptor);
+ copyAndClose(dataIn, bodyOut);
+ byte[] body = bodyOut.toByteArray();
+
+ GroupId groupId = new GroupId(getRandomId());
+ long timestamp = Long.MAX_VALUE;
+ Message message =
+ messageFactory.createMessage(groupId, timestamp, body);
+
+ // Check the size of the serialised message
+ int length = message.getRawLength();
+ assertTrue(length > UniqueId.LENGTH + 8
+ + 1 + MAX_CONTENT_TYPE_BYTES + MAX_IMAGE_SIZE);
+ assertTrue(length <= MAX_RECORD_PAYLOAD_BYTES);
+ }
+
@Test
public void testForumPostFitsIntoRecord() throws Exception {
// Create a maximum-length author
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/PrivateMessageValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/PrivateMessageValidatorTest.java
index 74ed605d3..cc42c0c87 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/PrivateMessageValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/PrivateMessageValidatorTest.java
@@ -31,7 +31,7 @@ import static org.briarproject.briar.api.attachment.MediaConstants.MAX_CONTENT_T
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE;
import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_DESCRIPTOR_LENGTH;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
-import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
+import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
@@ -55,7 +55,7 @@ public class PrivateMessageValidatorTest extends BrambleMockTestCase {
private final Message message = getMessage(group.getId());
private final long now = message.getTimestamp() + 1000;
private final String text =
- getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
+ getRandomString(MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH);
private final BdfList attachmentHeader = getAttachmentHeader();
private final MessageId attachmentId = new MessageId(getRandomId());
private final String contentType = getRandomString(MAX_CONTENT_TYPE_BYTES);
@@ -132,7 +132,7 @@ public class PrivateMessageValidatorTest extends BrambleMockTestCase {
@Test(expected = InvalidMessageException.class)
public void testRejectsTooLongTextForLegacyMessage() throws Exception {
String invalidText =
- getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH + 1);
+ getRandomString(MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH + 1);
testRejectsLegacyMessage(BdfList.of(invalidText));
}
@@ -190,7 +190,7 @@ public class PrivateMessageValidatorTest extends BrambleMockTestCase {
@Test(expected = InvalidMessageException.class)
public void testRejectsTooLongTextForPrivateMessage() throws Exception {
String invalidText =
- getRandomString(MAX_PRIVATE_MESSAGE_TEXT_LENGTH + 1);
+ getRandomString(MAX_PRIVATE_MESSAGE_INCOMING_TEXT_LENGTH + 1);
testRejectsPrivateMessage(
BdfList.of(PRIVATE_MESSAGE, invalidText, new BdfList()));
diff --git a/briar-headless/README.md b/briar-headless/README.md
index 3b37bd571..0b446467a 100644
--- a/briar-headless/README.md
+++ b/briar-headless/README.md
@@ -11,9 +11,11 @@ The REST API peer comes as a `jar` file
and needs a Java Runtime Environment (JRE) that supports at least Java 8.
It currently works only on GNU/Linux operating systems.
-To build the `jar` file, you can do this:
+To build the `jar` file, you need to specify the combination of architecture and platform:
- $ ./gradlew --configure-on-demand briar-headless:jar
+ $ ./gradlew --configure-on-demand briar-headless:x86LinuxJar
+ $ ./gradlew --configure-on-demand briar-headless:aarch64LinuxJar
+ $ ./gradlew --configure-on-demand briar-headless:armhfLinuxJar
You can start the peer (and its API server) like this:
diff --git a/briar-headless/build.gradle b/briar-headless/build.gradle
index 4357f06fe..53eae7bb0 100644
--- a/briar-headless/build.gradle
+++ b/briar-headless/build.gradle
@@ -44,18 +44,32 @@ dependencies {
kaptTest "com.google.dagger:dagger-compiler:$daggerVersion"
}
-jar {
- manifest {
+void jarFactory(Jar jarTask, jarArchitecture) {
+ jarTask.doFirst {
+ println 'Building ' + jarArchitecture + ' version has started'
+ }
+ jarTask.manifest {
attributes(
'Main-Class': 'org.briarproject.briar.headless.MainKt'
)
}
- from {
+ jarTask.setArchiveClassifier(jarArchitecture)
+ jarTask.from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
- doLast() {
+ {
+ String[] architectures = ["linux-aarch64", "linux-armhf", "linux-x86_64"]
+ for (String arch : architectures) {
+ if (arch != jarArchitecture) {
+ exclude "obfs4proxy_" + arch + ".zip"
+ exclude "tor_" + arch + ".zip"
+ }
+ }
+ }
+ jarTask.with jar
+ jarTask.doLast {
// Rename the original jar
- File jar = project.jar.archivePath
+ File jar = jarTask.archivePath
String srcPath = jar.toString().replaceFirst('\\.jar$', '.unsorted.jar')
File srcFile = new File(srcPath)
jar.renameTo(srcFile)
@@ -80,9 +94,22 @@ jar {
}
destStream.close()
srcJarFile.close()
+ println 'Building ' + jarArchitecture + ' version has finished'
}
}
+task aarch64LinuxJar(type: Jar) {
+ jarFactory(it, 'linux-aarch64')
+}
+
+task armhfLinuxJar(type: Jar) {
+ jarFactory(it, 'linux-armhf')
+}
+
+task x86LinuxJar(type: Jar) {
+ jarFactory(it, 'linux-x86_64')
+}
+
// At the moment for non-Android projects we need to explicitly mark the code generated by kapt
// as 'generated source code' for correct highlighting and resolve in IDE.
idea {