mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Refactor and add real shard payload to return shard activity and view model
This commit is contained in:
@@ -156,22 +156,13 @@
|
||||
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.socialbackup.recover.RecoverActivity"
|
||||
android:label="@string/activity_name_recovery"
|
||||
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity"
|
||||
android:label="@string/activity_name_recovery"
|
||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
||||
android:label="@string/activity_name_custodian_help_recovery"
|
||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
|
||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -183,14 +174,6 @@
|
||||
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity"
|
||||
android:label="@string/activity_name_custodian_help_recovery"
|
||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||
|
||||
@@ -8,8 +8,6 @@ import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
|
||||
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_TOP;
|
||||
|
||||
@@ -80,8 +80,7 @@ import org.briarproject.briar.android.sharing.ShareBlogFragment;
|
||||
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
||||
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
||||
import org.briarproject.briar.android.sharing.SharingModule;
|
||||
import org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity;
|
||||
import org.briarproject.briar.android.socialbackup.CustodianRecoveryModeExplainerFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.CustodianRecoveryModeExplainerFragment;
|
||||
import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
|
||||
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
|
||||
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
|
||||
@@ -89,14 +88,12 @@ import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardA
|
||||
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardSuccessFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeMainFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
|
||||
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.RecoverActivity;
|
||||
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
||||
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
|
||||
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
|
||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardActivity;
|
||||
import org.briarproject.briar.android.socialbackup.recover.ReturnShardFragment;
|
||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||
import org.briarproject.briar.android.test.TestDataActivity;
|
||||
|
||||
@@ -203,14 +200,12 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(NewOrRecoverActivity newOrRecoverActivity);
|
||||
|
||||
void inject(CustodianHelpRecoverActivity custodianHelpRecoverActivity);
|
||||
|
||||
void inject(ReturnShardActivity returnShardActivity);
|
||||
|
||||
void inject(CustodianReturnShardActivity custodianReturnShardActivity);
|
||||
|
||||
void inject(OwnerReturnShardActivity ownerReturnShardActivity);
|
||||
|
||||
void inject(OwnerRecoveryModeMainFragment ownerRecoveryModeMainFragment);
|
||||
|
||||
// Fragments
|
||||
|
||||
void inject(AuthorNameFragment fragment);
|
||||
@@ -273,8 +268,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(DistributedBackupActivity distributedBackupActivity);
|
||||
|
||||
void inject(RecoverActivity recoverActivity);
|
||||
|
||||
void inject(DatabaseComponent databaseComponent);
|
||||
|
||||
void inject(CustodianSelectorFragment custodianSelectorFragment);
|
||||
@@ -287,8 +280,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(NewOrRecoverFragment newOrRecoverFragment);
|
||||
|
||||
void inject(ReturnShardFragment returnShardFragment);
|
||||
|
||||
void inject(CustodianRecoveryModeExplainerFragment custodianRecoveryModeExplainerFragment);
|
||||
|
||||
void inject(CustodianReturnShardFragment custodianReturnShardFragment);
|
||||
|
||||
@@ -52,7 +52,7 @@ import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity;
|
||||
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.ImagePreview;
|
||||
@@ -399,7 +399,7 @@ public class ConversationActivity extends BriarActivity
|
||||
return true;
|
||||
case R.id.action_help_recover_account:
|
||||
if (contactId == null) return false;
|
||||
Intent i = new Intent(this, CustodianHelpRecoverActivity.class);
|
||||
Intent i = new Intent(this, CustodianReturnShardActivity.class);
|
||||
i.putExtra(CONTACT_ID, contactId.getInt());
|
||||
startActivity(i);
|
||||
return true;
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.briarproject.briar.android.socialbackup;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
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.socialbackup.recover.ReturnShardActivity;
|
||||
import org.briarproject.briar.api.socialbackup.MessageEncoder;
|
||||
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||
|
||||
public class CustodianHelpRecoverActivity extends BriarActivity implements
|
||||
BaseFragment.BaseFragmentListener, CustodianScanQrButtonListener {
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
public static final String RETURN_SHARD_PAYLOAD = "ReturnShardPayload";
|
||||
|
||||
private ContactId contactId;
|
||||
|
||||
@Inject
|
||||
public SocialBackupManager socialBackupManager;
|
||||
|
||||
// @Inject
|
||||
// public MessageEncoder messageEncoder;
|
||||
|
||||
@Inject
|
||||
public DatabaseComponent db;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recover); // TODO change this
|
||||
|
||||
Intent intent = getIntent();
|
||||
int id = intent.getIntExtra(CONTACT_ID, -1);
|
||||
if (id == -1) throw new IllegalStateException("No ContactId");
|
||||
contactId = new ContactId(id);
|
||||
|
||||
// check if we have a shard for this secret owner
|
||||
try {
|
||||
db.transaction(false, txn -> {
|
||||
if (!socialBackupManager.amCustodian(txn, contactId)) {
|
||||
throw new DbException();
|
||||
}
|
||||
CustodianRecoveryModeExplainerFragment fragment =
|
||||
new CustodianRecoveryModeExplainerFragment();
|
||||
showInitialFragment(fragment);
|
||||
});
|
||||
} catch (DbException e) {
|
||||
// TODO improve this
|
||||
Toast.makeText(this,
|
||||
"You do not hold a backup shard from this contact",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanQrButtonClicked() {
|
||||
try {
|
||||
db.transaction(false, txn -> {
|
||||
byte[] returnShardPayloadBytes = socialBackupManager
|
||||
.getReturnShardPayloadBytes(txn, contactId);
|
||||
|
||||
Intent i = new Intent(this, ReturnShardActivity.class);
|
||||
i.putExtra(RETURN_SHARD_PAYLOAD, returnShardPayloadBytes);
|
||||
startActivity(i);
|
||||
});
|
||||
} catch (DbException e) {
|
||||
Toast.makeText(this,
|
||||
"Error reading social backup from storage",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.socialbackup;
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -9,7 +9,6 @@ import android.widget.Button;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardViewModel;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
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.socialbackup.CustodianRecoveryModeExplainerFragment;
|
||||
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
|
||||
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
@@ -19,6 +21,7 @@ import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||
|
||||
public class CustodianReturnShardActivity extends BriarActivity
|
||||
implements BaseFragment.BaseFragmentListener {
|
||||
@@ -41,20 +44,21 @@ public class CustodianReturnShardActivity extends BriarActivity
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
// byte[] returnShardPayloadBytes =
|
||||
// getIntent().getByteArrayExtra(RETURN_SHARD_PAYLOAD);
|
||||
// try {
|
||||
// ReturnShardPayload returnShardPayload = parseReturnShardPayload(
|
||||
// clientHelper.toList(returnShardPayloadBytes));
|
||||
// viewModel.setReturnShardPayload(returnShardPayload);
|
||||
// } catch (FormatException e) {
|
||||
// Toast.makeText(this,
|
||||
// "Error reading social backup",
|
||||
// Toast.LENGTH_SHORT).show();
|
||||
// finish();
|
||||
// }
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
if (state == null) {
|
||||
Intent intent = getIntent();
|
||||
int id = intent.getIntExtra(CONTACT_ID, -1);
|
||||
if (id == -1) throw new IllegalStateException("No ContactId");
|
||||
ContactId contactId = new ContactId(id);
|
||||
|
||||
try {
|
||||
viewModel.start(contactId);
|
||||
} catch (DbException e) {
|
||||
Toast.makeText(this,
|
||||
"You do not hold a backup piece for this contact",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
showInitialFragment(new CustodianRecoveryModeExplainerFragment());
|
||||
}
|
||||
viewModel.getShowCameraFragment().observeEvent(this, show -> {
|
||||
@@ -71,6 +75,13 @@ public class CustodianReturnShardActivity extends BriarActivity
|
||||
if (state instanceof CustodianTask.State.Success) {
|
||||
CustodianReturnShardSuccessFragment fragment = new CustodianReturnShardSuccessFragment();
|
||||
showNextFragment(fragment);
|
||||
} else if (state instanceof CustodianTask.State.Failure) {
|
||||
// TODO error fragment here
|
||||
// TODO handle reason
|
||||
Toast.makeText(this,
|
||||
"Backup piece transfer failed",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,19 @@ import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
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.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
|
||||
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -44,6 +43,9 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
|
||||
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Executor ioExecutor;
|
||||
private final SocialBackupManager socialBackupManager;
|
||||
private final DatabaseComponent db;
|
||||
final QrCodeDecoder qrCodeDecoder;
|
||||
private boolean wasContinueClicked = false;
|
||||
private boolean qrCodeRead = false;
|
||||
private final MutableLiveEvent<Boolean> showCameraFragment =
|
||||
@@ -52,8 +54,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
|
||||
new MutableLiveEvent<>();
|
||||
private final MutableLiveData<CustodianTask.State> state =
|
||||
new MutableLiveData<>();
|
||||
final QrCodeDecoder qrCodeDecoder;
|
||||
private final CustodianTask task;
|
||||
private byte[] returnShardPayloadBytes;
|
||||
|
||||
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
||||
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
@@ -62,18 +64,32 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
|
||||
public CustodianReturnShardViewModel(
|
||||
@NonNull Application application,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
SocialBackupManager socialBackupManager,
|
||||
DatabaseComponent db,
|
||||
CustodianTask task,
|
||||
AndroidExecutor androidExecutor) {
|
||||
super(application);
|
||||
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.socialBackupManager = socialBackupManager;
|
||||
this.db = db;
|
||||
this.task = task;
|
||||
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
|
||||
task.cancel();
|
||||
task.start(this, "replace this with the shard".getBytes());
|
||||
}
|
||||
|
||||
public void start(ContactId contactId) throws DbException {
|
||||
db.transaction(false, txn -> {
|
||||
if (!socialBackupManager.amCustodian(txn, contactId)) {
|
||||
throw new DbException();
|
||||
}
|
||||
returnShardPayloadBytes = socialBackupManager
|
||||
.getReturnShardPayloadBytes(txn, contactId);
|
||||
});
|
||||
task.cancel();
|
||||
task.start(this, returnShardPayloadBytes);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
@Override
|
||||
public void onQrCodeDecoded(Result result) {
|
||||
|
||||
@@ -50,7 +50,7 @@ public class OwnerRecoveryModeExplainerFragment extends BaseFragment {
|
||||
View view = inflater.inflate(R.layout.fragment_recovery_owner_explainer,
|
||||
container, false);
|
||||
Button button = view.findViewById(R.id.beginButton);
|
||||
button.setOnClickListener(e -> viewModel.onContinueClicked());
|
||||
button.setOnClickListener(e -> viewModel.onStartClicked());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.socialbackup;
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -9,44 +9,47 @@ import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.socialbackup.ScanQrButtonListener;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
public class OwnerRecoveryModeMainFragment extends BaseFragment {
|
||||
|
||||
protected ScanQrButtonListener listener;
|
||||
|
||||
public static final String NUM_RECOVERED = "num_recovered";
|
||||
|
||||
public static final String TAG =
|
||||
OwnerRecoveryModeMainFragment.class.getName();
|
||||
|
||||
public static OwnerRecoveryModeMainFragment newInstance(int numRecovered) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(NUM_RECOVERED, numRecovered);
|
||||
OwnerRecoveryModeMainFragment fragment =
|
||||
new OwnerRecoveryModeMainFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private int numShards;
|
||||
private OwnerReturnShardViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(OwnerReturnShardViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requireActivity().setTitle(R.string.title_recovery_mode);
|
||||
|
||||
Bundle args = requireArguments();
|
||||
numShards = args.getInt(NUM_RECOVERED);
|
||||
}
|
||||
// @Override
|
||||
// public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
// super.onCreate(savedInstanceState);
|
||||
// requireActivity().setTitle(R.string.title_recovery_mode);
|
||||
//
|
||||
// Bundle args = requireArguments();
|
||||
// }
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -57,17 +60,10 @@ public class OwnerRecoveryModeMainFragment extends BaseFragment {
|
||||
container, false);
|
||||
|
||||
TextView textViewCount = view.findViewById(R.id.textViewShardCount);
|
||||
textViewCount.setText(String.format("%d", numShards));
|
||||
textViewCount.setText(String.format("%d", viewModel.getNumberOfShards()));
|
||||
|
||||
Button button = view.findViewById(R.id.button);
|
||||
button.setOnClickListener(e -> listener.scanQrButtonClicked());
|
||||
button.setOnClickListener(e -> viewModel.onContinueClicked());
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
listener = (ScanQrButtonListener) context;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
@@ -51,23 +52,6 @@ public class OwnerReturnShardActivity extends BaseActivity
|
||||
.get(OwnerReturnShardViewModel.class);
|
||||
}
|
||||
|
||||
// TODO the following two methods should be injected from messageParser
|
||||
// private Shard parseShardMessage(BdfList body) throws FormatException {
|
||||
// // Message type, secret ID, shard
|
||||
// byte[] secretId = body.getRaw(1);
|
||||
// byte[] shard = body.getRaw(2);
|
||||
// return new Shard(secretId, shard);
|
||||
// }
|
||||
//
|
||||
// private ReturnShardPayload parseReturnShardPayload(BdfList body)
|
||||
// throws FormatException {
|
||||
// checkSize(body, 2);
|
||||
// Shard shard = parseShardMessage(body.getList(0));
|
||||
// org.briarproject.briar.api.socialbackup.BackupPayload backupPayload =
|
||||
// new BackupPayload(body.getRaw(1));
|
||||
// return new ReturnShardPayload(shard, backupPayload);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
@@ -79,6 +63,11 @@ public class OwnerReturnShardActivity extends BaseActivity
|
||||
viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
|
||||
if (show) showQrCodeFragment();
|
||||
});
|
||||
viewModel.getStartClicked().observeEvent(this, start -> {
|
||||
if (start) {
|
||||
showNextFragment(new OwnerRecoveryModeMainFragment());
|
||||
}
|
||||
});
|
||||
viewModel.getState()
|
||||
.observe(this, this::onReturnShardStateChanged);
|
||||
}
|
||||
@@ -135,13 +124,15 @@ public class OwnerReturnShardActivity extends BaseActivity
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onReturnShardStateChanged(SecretOwnerTask.State state) {
|
||||
if (state instanceof SecretOwnerTask.State.Success) {
|
||||
byte[] shardPayload = ((SecretOwnerTask.State.Success) state).getRemotePayload();
|
||||
ReturnShardPayload shardPayload = ((SecretOwnerTask.State.Success) state).getRemotePayload();
|
||||
boolean added = viewModel.addToShardSet(shardPayload);
|
||||
Toast.makeText(this,
|
||||
"Success - got shard " + shardPayload.length,
|
||||
"Success - got shard" + (added ? "" : " duplicate"),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
// finish();
|
||||
} else if (state instanceof SecretOwnerTask.State.Failure) {
|
||||
// TODO error screen, handle reason
|
||||
Toast.makeText(this,
|
||||
|
||||
@@ -12,11 +12,13 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.android.contact.add.nearby.QrCodeUtils;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -51,8 +53,11 @@ class OwnerReturnShardViewModel extends AndroidViewModel
|
||||
new MutableLiveEvent<>();
|
||||
private final MutableLiveData<SecretOwnerTask.State> state =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveEvent<Boolean> startClicked =
|
||||
new MutableLiveEvent<>();
|
||||
private boolean wasContinueClicked = false;
|
||||
private boolean isActivityResumed = false;
|
||||
private ArrayList<ReturnShardPayload> recoveredShards = new ArrayList<>();
|
||||
private Bitmap qrCodeBitmap;
|
||||
private WifiManager wifiManager;
|
||||
|
||||
@@ -101,6 +106,11 @@ class OwnerReturnShardViewModel extends AndroidViewModel
|
||||
stopListening();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onStartClicked() {
|
||||
startClicked.setEvent(true);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onContinueClicked() {
|
||||
wasContinueClicked = true;
|
||||
@@ -159,6 +169,10 @@ class OwnerReturnShardViewModel extends AndroidViewModel
|
||||
return showQrCodeFragment;
|
||||
}
|
||||
|
||||
LiveEvent<Boolean> getStartClicked() {
|
||||
return startClicked;
|
||||
}
|
||||
|
||||
LiveData<SecretOwnerTask.State> getState() {
|
||||
return state;
|
||||
}
|
||||
@@ -168,6 +182,10 @@ class OwnerReturnShardViewModel extends AndroidViewModel
|
||||
return qrCodeBitmap;
|
||||
}
|
||||
|
||||
public int getNumberOfShards() {
|
||||
return recoveredShards.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(SecretOwnerTask.State state) {
|
||||
this.state.postValue(state);
|
||||
@@ -187,4 +205,22 @@ class OwnerReturnShardViewModel extends AndroidViewModel
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out how to actually use a set for these objects
|
||||
public boolean addToShardSet(ReturnShardPayload toAdd) {
|
||||
boolean found = false;
|
||||
for (ReturnShardPayload returnShardPayload : recoveredShards) {
|
||||
if (toAdd.equals(returnShardPayload)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) recoveredShards.add(toAdd);
|
||||
return !found;
|
||||
}
|
||||
|
||||
public boolean canRecover() {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
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
|
||||
BaseFragment.BaseFragmentListener, ExplainerDismissedListener,
|
||||
ScanQrButtonListener {
|
||||
|
||||
private int numRecovered;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recover);
|
||||
|
||||
numRecovered = 0; // TODO - retrieve this from somewhere
|
||||
|
||||
// only show the explainer if we have no shards
|
||||
if (numRecovered == 0) {
|
||||
OwnerRecoveryModeExplainerFragment fragment =
|
||||
new OwnerRecoveryModeExplainerFragment();
|
||||
showInitialFragment(fragment);
|
||||
} else {
|
||||
OwnerRecoveryModeMainFragment fragment =
|
||||
OwnerRecoveryModeMainFragment.newInstance(numRecovered);
|
||||
showInitialFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void explainerDismissed() {
|
||||
OwnerRecoveryModeMainFragment fragment =
|
||||
OwnerRecoveryModeMainFragment.newInstance(numRecovered);
|
||||
showNextFragment(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanQrButtonClicked() {
|
||||
// TODO
|
||||
Toast.makeText(this,
|
||||
"coming soon...",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void runOnDbThread(Runnable runnable) {
|
||||
throw new RuntimeException("Don't use this deprecated method here.");
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
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.BaseActivity;
|
||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactErrorFragment;
|
||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.util.RequestBluetoothDiscoverable;
|
||||
import org.briarproject.briar.api.socialbackup.BackupPayload;
|
||||
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
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.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.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
import static org.briarproject.briar.android.socialbackup.CustodianHelpRecoverActivity.RETURN_SHARD_PAYLOAD;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ReturnShardActivity extends BaseActivity
|
||||
implements BaseFragment.BaseFragmentListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ReturnShardActivity.class.getName());
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
// @Inject
|
||||
// MessageParser messageParser;
|
||||
|
||||
@Inject
|
||||
public ClientHelper clientHelper;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
// TODO the following two methods should be injected from messageParser
|
||||
private Shard parseShardMessage(BdfList body) throws FormatException {
|
||||
// Message type, secret ID, shard
|
||||
byte[] secretId = body.getRaw(1);
|
||||
byte[] shard = body.getRaw(2);
|
||||
return new Shard(secretId, shard);
|
||||
}
|
||||
|
||||
private ReturnShardPayload parseReturnShardPayload(BdfList body)
|
||||
throws FormatException {
|
||||
checkSize(body, 2);
|
||||
Shard shard = parseShardMessage(body.getList(0));
|
||||
org.briarproject.briar.api.socialbackup.BackupPayload backupPayload =
|
||||
new BackupPayload(body.getRaw(1));
|
||||
return new ReturnShardPayload(shard, backupPayload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
byte[] returnShardPayloadBytes =
|
||||
getIntent().getByteArrayExtra(RETURN_SHARD_PAYLOAD);
|
||||
if (returnShardPayloadBytes != null) {
|
||||
try {
|
||||
ReturnShardPayload returnShardPayload = parseReturnShardPayload(
|
||||
clientHelper.toList(returnShardPayloadBytes));
|
||||
viewModel.setSending(true);
|
||||
viewModel.setReturnShardPayload(returnShardPayload);
|
||||
} catch (FormatException e) {
|
||||
Toast.makeText(this,
|
||||
"Error reading social backup",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
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();
|
||||
});
|
||||
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(ReturnShardFragment.TAG) == null) {
|
||||
BaseFragment f = ReturnShardFragment.newInstance();
|
||||
fm.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||
.addToBackStack(f.getUniqueTag())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void onReturnShardStateChanged(ReturnShardState state) {
|
||||
if (state instanceof ReturnShardState.SocialBackupExchangeFinished) {
|
||||
ReturnShardState.SocialBackupExchangeResult result =
|
||||
((ReturnShardState.SocialBackupExchangeFinished) state).result;
|
||||
onSocialBackupExchangeResult(result);
|
||||
} else if (state instanceof ReturnShardState.Failed) {
|
||||
Boolean qrCodeTooOld =
|
||||
((ReturnShardState.Failed) state).qrCodeTooOld;
|
||||
onAddingContactFailed(qrCodeTooOld);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSocialBackupExchangeResult(
|
||||
ReturnShardState.SocialBackupExchangeResult result) {
|
||||
if (result instanceof ReturnShardState.SocialBackupExchangeResult.Success) {
|
||||
// String text = getString(R.string.contact_added_toast, contactName);
|
||||
Toast.makeText(this, "Shard return successful", LENGTH_LONG).show();
|
||||
supportFinishAfterTransition();
|
||||
} else if (result instanceof ReturnShardState.SocialBackupExchangeResult.Error) {
|
||||
showErrorFragment();
|
||||
} 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());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void runOnDbThread(Runnable runnable) {
|
||||
throw new RuntimeException("Don't use this deprecated method here.");
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
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.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.CameraException;
|
||||
import org.briarproject.briar.android.contact.add.nearby.CameraView;
|
||||
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 ReturnShardFragment extends BaseFragment
|
||||
implements QrCodeView.FullscreenListener {
|
||||
|
||||
public static final String TAG = org.briarproject.briar.android.contact.add.nearby.AddNearbyContactFragment.class.getName();
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private ReturnShardViewModel viewModel;
|
||||
private CameraView cameraView;
|
||||
private LinearLayout cameraOverlay;
|
||||
private View statusView;
|
||||
private QrCodeView qrCodeView;
|
||||
private TextView status;
|
||||
|
||||
public static ReturnShardFragment newInstance() {
|
||||
Bundle args = new Bundle();
|
||||
ReturnShardFragment fragment = new ReturnShardFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(ReturnShardViewModel.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::onReturnShardStateChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
cameraView.setPreviewConsumer(viewModel.getQrCodeDecoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
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 LinearLayout.LayoutParams(0, 0, 0f);
|
||||
qrCodeParams = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
|
||||
} else {
|
||||
// Shrink the QR code view to fill half its parent
|
||||
if (cameraOverlay.getOrientation() == HORIZONTAL) {
|
||||
statusParams = new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
||||
qrCodeParams = new LinearLayout.LayoutParams(0, MATCH_PARENT, 1f);
|
||||
} else {
|
||||
statusParams = new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
||||
qrCodeParams = new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f);
|
||||
}
|
||||
}
|
||||
statusView.setLayoutParams(statusParams);
|
||||
qrCodeView.setLayoutParams(qrCodeParams);
|
||||
cameraOverlay.invalidate();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onReturnShardStateChanged(@Nullable ReturnShardState state) {
|
||||
if (state instanceof ReturnShardState.KeyAgreementListening) {
|
||||
Bitmap qrCode =
|
||||
((ReturnShardState.KeyAgreementListening) state).qrCode;
|
||||
qrCodeView.setQrCode(qrCode);
|
||||
} else if (state instanceof ReturnShardState.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 ReturnShardState.KeyAgreementWaiting) {
|
||||
status.setText(R.string.waiting_for_contact_to_scan);
|
||||
} else if (state instanceof ReturnShardState.KeyAgreementStarted) {
|
||||
qrCodeView.setVisibility(INVISIBLE);
|
||||
status.setText(R.string.authenticating_with_device);
|
||||
} else if (state instanceof ReturnShardState.SocialBackupExchangeStarted) {
|
||||
status.setText(R.string.exchanging_contact_details);
|
||||
} else if (state instanceof ReturnShardState.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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
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 SocialBackupExchangeStarted extends ReturnShardState {
|
||||
}
|
||||
|
||||
static class SocialBackupExchangeFinished extends ReturnShardState {
|
||||
final SocialBackupExchangeResult
|
||||
result;
|
||||
|
||||
SocialBackupExchangeFinished(
|
||||
SocialBackupExchangeResult 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 SocialBackupExchangeResult {
|
||||
static class Success extends SocialBackupExchangeResult {
|
||||
Success() {}
|
||||
}
|
||||
|
||||
static class Error extends SocialBackupExchangeResult {
|
||||
@Nullable
|
||||
final Author duplicateAuthor;
|
||||
|
||||
Error(@Nullable Author duplicateAuthor) {
|
||||
this.duplicateAuthor = duplicateAuthor;
|
||||
}
|
||||
}
|
||||
} // end ContactExchangeResult
|
||||
|
||||
}
|
||||
@@ -1,545 +0,0 @@
|
||||
package org.briarproject.briar.android.socialbackup.recover;
|
||||
|
||||
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 org.briarproject.briar.api.socialbackup.ReturnShardPayload;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
|
||||
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 boolean sending;
|
||||
private ReturnShardPayload returnShardPayload;
|
||||
|
||||
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 SocialBackupExchangeManager socialBackupExchangeManager;
|
||||
private final SocialBackupManager socialBackupManager;
|
||||
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,
|
||||
SocialBackupExchangeManager socialBackupExchangeManager,
|
||||
SocialBackupManager socialBackupManager,
|
||||
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.socialBackupExchangeManager = socialBackupExchangeManager;
|
||||
this.socialBackupManager = socialBackupManager;
|
||||
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.SocialBackupExchangeStarted());
|
||||
} 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"+gotLocalPayload+gotRemotePayload);
|
||||
// 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 {
|
||||
if (sending) {
|
||||
socialBackupExchangeManager.sendReturnShard(conn, masterKey, alice, returnShardPayload);
|
||||
} else {
|
||||
returnShardPayload = socialBackupExchangeManager.receiveReturnShard(conn, masterKey, alice);
|
||||
}
|
||||
ReturnShardState.SocialBackupExchangeResult.Success
|
||||
success =
|
||||
new ReturnShardState.SocialBackupExchangeResult.Success();
|
||||
state.postValue(
|
||||
new ReturnShardState.SocialBackupExchangeFinished(success));
|
||||
} catch (ContactExistsException e) {
|
||||
tryToClose(conn);
|
||||
ReturnShardState.SocialBackupExchangeResult.Error
|
||||
error = new ReturnShardState.SocialBackupExchangeResult.Error(
|
||||
e.getRemoteAuthor());
|
||||
state.postValue(
|
||||
new ReturnShardState.SocialBackupExchangeFinished(error));
|
||||
} catch (DbException | IOException e) {
|
||||
tryToClose(conn);
|
||||
logException(LOG, WARNING, e);
|
||||
ReturnShardState.SocialBackupExchangeResult.Error
|
||||
error =
|
||||
new ReturnShardState.SocialBackupExchangeResult.Error(null);
|
||||
state.postValue(
|
||||
new ReturnShardState.SocialBackupExchangeFinished(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;
|
||||
}
|
||||
|
||||
public void setSending(boolean sending) {
|
||||
this.sending = sending;
|
||||
}
|
||||
|
||||
public void setReturnShardPayload(ReturnShardPayload returnShardPayload) {
|
||||
this.returnShardPayload = returnShardPayload;
|
||||
}
|
||||
|
||||
QrCodeDecoder getQrCodeDecoder() {
|
||||
return qrCodeDecoder;
|
||||
}
|
||||
}
|
||||
@@ -675,13 +675,13 @@
|
||||
|
||||
<!-- recovery from the secret owner's POV -->
|
||||
|
||||
<string name="recovery_explainer">You need to meet your trusted contacts in-person to receive pieces by scanning QR codes</string>
|
||||
<string name="recovery_explainer">You need to meet your trusted contacts in-person to receive pieces</string>
|
||||
<string name="recovery_begin">Begin</string>
|
||||
<string name="recovery_failed_to_receive">Failed to receive backup piece</string>
|
||||
<string name="recovery_helpful_suggestions">Please check that bluetooth is swtiched on and that no-one but your trusted contact is able to scan the QR code</string>
|
||||
<string name="recovery_retry">Retry</string>
|
||||
<string name="recovery_recovered_shards">Recovered backup pieces:</string>
|
||||
<string name="recovery_scan_qr_code">Scan QR code</string>
|
||||
<string name="recovery_scan_qr_code">Show QR code</string>
|
||||
<string name="recovery_recovering_account">Recovering account…</string>
|
||||
<string name="recovery_shard_received">Account backup piece received</string>
|
||||
<string name="recovery_account_recovered">Account recovered</string>
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<intent
|
||||
android:targetClass="org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity"
|
||||
android:targetClass="org.briarproject.briar.android.socialbackup.DistributedBackupActivity"
|
||||
android:targetPackage="@string/app_package" />
|
||||
|
||||
</Preference>
|
||||
|
||||
Reference in New Issue
Block a user