mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
return shard activity and view model
This commit is contained in:
@@ -157,7 +157,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.socialbackup.RecoverActivity"
|
android:name="org.briarproject.briar.android.socialbackup.recover.RecoverActivity"
|
||||||
android:label="@string/activity_name_recovery"
|
android:label="@string/activity_name_recovery"
|
||||||
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
|
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.RecoverActivity;
|
import org.briarproject.briar.android.socialbackup.recover.RecoverActivity;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity;
|
||||||
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
@@ -45,7 +46,7 @@ public class NewOrRecoverActivity extends BaseActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void recoverAccountChosen() {
|
public void recoverAccountChosen() {
|
||||||
finish();
|
finish();
|
||||||
Intent i = new Intent(this, RecoverActivity.class);
|
Intent i = new Intent(this, ReturnShardActivity.class);
|
||||||
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
|
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
|
||||||
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
|
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
|
|||||||
@@ -85,11 +85,12 @@ import org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity;
|
|||||||
import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
|
import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
|
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.OwnerRecoveryModeExplainerFragment;
|
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.RecoverActivity;
|
import org.briarproject.briar.android.socialbackup.recover.RecoverActivity;
|
||||||
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
|
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
|
||||||
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
|
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
|
||||||
|
import org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity;
|
||||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||||
import org.briarproject.briar.android.test.TestDataActivity;
|
import org.briarproject.briar.android.test.TestDataActivity;
|
||||||
|
|
||||||
@@ -198,6 +199,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(CustodianHelpRecoverActivity custodianHelpRecoverActivity);
|
void inject(CustodianHelpRecoverActivity custodianHelpRecoverActivity);
|
||||||
|
|
||||||
|
void inject(ReturnShardActivity returnShardActivity);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
|
|
||||||
void inject(AuthorNameFragment fragment);
|
void inject(AuthorNameFragment fragment);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.briarproject.briar.android.socialbackup;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -12,15 +10,22 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
||||||
|
|
||||||
protected ExplainerDismissedListener listener;
|
|
||||||
public static final String TAG =
|
public static final String TAG =
|
||||||
OwnerRecoveryModeExplainerFragment.class.getName();
|
OwnerRecoveryModeExplainerFragment.class.getName();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private ReturnShardViewModel viewModel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -35,20 +40,16 @@ public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
|||||||
View view = inflater.inflate(R.layout.fragment_recovery_owner_explainer,
|
View view = inflater.inflate(R.layout.fragment_recovery_owner_explainer,
|
||||||
container, false);
|
container, false);
|
||||||
Button button = view.findViewById(R.id.beginButton);
|
Button button = view.findViewById(R.id.beginButton);
|
||||||
button.setOnClickListener(e -> listener.explainerDismissed());
|
button.setOnClickListener(e -> viewModel.onContinueClicked());
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
listener = (ExplainerDismissedListener) context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(ReturnShardViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.socialbackup;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@@ -7,6 +7,9 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.ExplainerDismissedListener;
|
||||||
|
import org.briarproject.briar.android.socialbackup.OwnerRecoveryModeMainFragment;
|
||||||
|
import org.briarproject.briar.android.socialbackup.ScanQrButtonListener;
|
||||||
|
|
||||||
public class RecoverActivity extends BaseActivity implements
|
public class RecoverActivity extends BaseActivity implements
|
||||||
BaseFragment.BaseFragmentListener, ExplainerDismissedListener,
|
BaseFragment.BaseFragmentListener, ExplainerDismissedListener,
|
||||||
|
|||||||
@@ -1,4 +1,238 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
public class ReturnShardActivity {
|
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.AddNearbyContactErrorFragment;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactFragment;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class ReturnShardActivity extends BriarActivity
|
||||||
|
implements BaseFragment.BaseFragmentListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(
|
||||||
|
org.briarproject.briar.android.contact.add.nearby.AddNearbyContactActivity.class
|
||||||
|
.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
|
private ReturnShardViewModel viewModel;
|
||||||
|
private AddNearbyContactPermissionManager permissionManager;
|
||||||
|
|
||||||
|
private final ActivityResultLauncher<String[]> permissionLauncher =
|
||||||
|
registerForActivityResult(
|
||||||
|
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||||
|
r ->
|
||||||
|
permissionManager.onRequestPermissionResult(r,
|
||||||
|
viewModel::showQrCodeFragmentIfAllowed));
|
||||||
|
private final ActivityResultLauncher<Integer> bluetoothLauncher =
|
||||||
|
registerForActivityResult(new RequestBluetoothDiscoverable(),
|
||||||
|
this::onBluetoothDiscoverableResult);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||||
|
.get(ReturnShardViewModel.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(getExplainerFragment());
|
||||||
|
}
|
||||||
|
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::onReturnShardStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseFragment getExplainerFragment() {
|
||||||
|
return new OwnerRecoveryModeExplainerFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(
|
||||||
|
ReturnShardViewModel.BluetoothDecision.ACCEPTED);
|
||||||
|
} else {
|
||||||
|
LOG.info("Bluetooth discoverability was refused");
|
||||||
|
viewModel.setBluetoothDecision(
|
||||||
|
ReturnShardViewModel.BluetoothDecision.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 ReturnShardState.Failed) {
|
||||||
|
// re-create this activity when going back in failed state
|
||||||
|
Intent i = new Intent(this, ReturnShardActivity.class);
|
||||||
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(i);
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestBluetoothDiscoverable() {
|
||||||
|
if (!viewModel.isBluetoothSupported()) {
|
||||||
|
viewModel.setBluetoothDecision(
|
||||||
|
ReturnShardViewModel.BluetoothDecision.NO_ADAPTER);
|
||||||
|
} else {
|
||||||
|
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||||
|
if (i.resolveActivity(getPackageManager()) != null) {
|
||||||
|
LOG.info("Asking for Bluetooth discoverability");
|
||||||
|
viewModel.setBluetoothDecision(
|
||||||
|
ReturnShardViewModel.BluetoothDecision.WAITING);
|
||||||
|
bluetoothLauncher.launch(120); // 2min discoverable
|
||||||
|
} else {
|
||||||
|
viewModel.setBluetoothDecision(
|
||||||
|
ReturnShardViewModel.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 onReturnShardStateChanged(ReturnShardState state) {
|
||||||
|
if (state instanceof ReturnShardState.ContactExchangeFinished) {
|
||||||
|
ReturnShardState.ContactExchangeResult result =
|
||||||
|
((ReturnShardState.ContactExchangeFinished) state).result;
|
||||||
|
onContactExchangeResult(result);
|
||||||
|
} else if (state instanceof ReturnShardState.Failed) {
|
||||||
|
Boolean qrCodeTooOld =
|
||||||
|
((ReturnShardState.Failed) state).qrCodeTooOld;
|
||||||
|
onAddingContactFailed(qrCodeTooOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onContactExchangeResult(
|
||||||
|
ReturnShardState.ContactExchangeResult result) {
|
||||||
|
if (result instanceof ReturnShardState.ContactExchangeResult.Success) {
|
||||||
|
Author remoteAuthor =
|
||||||
|
((ReturnShardState.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 ReturnShardState.ContactExchangeResult.Error) {
|
||||||
|
Author duplicateAuthor =
|
||||||
|
((ReturnShardState.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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,80 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
public class ReturnShardState {
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
abstract class ReturnShardState {
|
||||||
|
|
||||||
|
static class KeyAgreementListening extends
|
||||||
|
ReturnShardState {
|
||||||
|
final Bitmap qrCode;
|
||||||
|
|
||||||
|
KeyAgreementListening(Bitmap qrCode) {
|
||||||
|
this.qrCode = qrCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class QrCodeScanned extends
|
||||||
|
ReturnShardState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeyAgreementWaiting extends ReturnShardState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeyAgreementStarted extends ReturnShardState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ContactExchangeStarted extends ReturnShardState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ContactExchangeFinished extends ReturnShardState {
|
||||||
|
final ContactExchangeResult
|
||||||
|
result;
|
||||||
|
|
||||||
|
ContactExchangeFinished(
|
||||||
|
ContactExchangeResult result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Failed extends ReturnShardState {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,526 @@
|
|||||||
package org.briarproject.briar.android.socialbackup.recover;
|
package org.briarproject.briar.android.socialbackup.recover;
|
||||||
|
|
||||||
public class ReturnShardViewModel {
|
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.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.QrCodeDecoder;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.QrCodeUtils;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ReturnShardViewModel extends AndroidViewModel
|
||||||
|
implements EventListener, QrCodeDecoder.ResultCallback {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ReturnShardViewModel.class.getName());
|
||||||
|
|
||||||
|
// TODO deduplicate
|
||||||
|
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<KeyAgreementTask> keyAgreementTaskProvider;
|
||||||
|
private final ContactExchangeManager contactExchangeManager;
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
|
|
||||||
|
private final MutableLiveEvent<Boolean> checkPermissions =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveEvent<Boolean> requestBluetoothDiscoverable =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveEvent<Boolean> showQrCodeFragment =
|
||||||
|
new MutableLiveEvent<>();
|
||||||
|
private final MutableLiveData<ReturnShardState> 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
|
||||||
|
ReturnShardViewModel(Application app,
|
||||||
|
EventBus eventBus,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
@IoExecutor Executor ioExecutor,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
PayloadEncoder payloadEncoder,
|
||||||
|
PayloadParser payloadParser,
|
||||||
|
Provider<KeyAgreementTask> 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 == BluetoothDecision.REFUSED) {
|
||||||
|
bluetoothDecision = 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
|
||||||
|
Plugin.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;
|
||||||
|
Plugin.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;
|
||||||
|
Plugin.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 = 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 ReturnShardState.KeyAgreementWaiting());
|
||||||
|
} else if (e instanceof KeyAgreementStartedEvent) {
|
||||||
|
LOG.info("KeyAgreementStartedEvent received");
|
||||||
|
state.setValue(new ReturnShardState.KeyAgreementStarted());
|
||||||
|
} else if (e instanceof KeyAgreementFinishedEvent) {
|
||||||
|
LOG.info("KeyAgreementFinishedEvent received");
|
||||||
|
KeyAgreementResult result =
|
||||||
|
((KeyAgreementFinishedEvent) e).getResult();
|
||||||
|
startContactExchange(result);
|
||||||
|
state.setValue(new ReturnShardState.ContactExchangeStarted());
|
||||||
|
} else if (e instanceof KeyAgreementAbortedEvent) {
|
||||||
|
LOG.info("KeyAgreementAbortedEvent received");
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.setValue(new ReturnShardState.Failed());
|
||||||
|
} else if (e instanceof KeyAgreementFailedEvent) {
|
||||||
|
LOG.info("KeyAgreementFailedEvent received");
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.setValue(new ReturnShardState.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 == BluetoothDecision.UNKNOWN) {
|
||||||
|
requestBluetoothDiscoverable.setEvent(true);
|
||||||
|
} else if (bluetoothDecision == 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 ReturnShardState.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 ReturnShardState.QrCodeScanned());
|
||||||
|
} catch (UnsupportedVersionException e) {
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.postValue(new ReturnShardState.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 ReturnShardState.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);
|
||||||
|
ReturnShardState.ContactExchangeResult.Success
|
||||||
|
success =
|
||||||
|
new ReturnShardState.ContactExchangeResult.Success(
|
||||||
|
contact.getAuthor());
|
||||||
|
state.postValue(
|
||||||
|
new ReturnShardState.ContactExchangeFinished(success));
|
||||||
|
} catch (ContactExistsException e) {
|
||||||
|
tryToClose(conn);
|
||||||
|
ReturnShardState.ContactExchangeResult.Error
|
||||||
|
error = new ReturnShardState.ContactExchangeResult.Error(
|
||||||
|
e.getRemoteAuthor());
|
||||||
|
state.postValue(
|
||||||
|
new ReturnShardState.ContactExchangeFinished(error));
|
||||||
|
} catch (DbException | IOException e) {
|
||||||
|
tryToClose(conn);
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
ReturnShardState.ContactExchangeResult.Error
|
||||||
|
error =
|
||||||
|
new ReturnShardState.ContactExchangeResult.Error(null);
|
||||||
|
state.postValue(
|
||||||
|
new ReturnShardState.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<Boolean> getCheckPermissions() {
|
||||||
|
return checkPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getRequestBluetoothDiscoverable() {
|
||||||
|
return requestBluetoothDiscoverable;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveEvent<Boolean> getShowQrCodeFragment() {
|
||||||
|
return showQrCodeFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<ReturnShardState> getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user