diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index b590e7acf..30f3ab4a4 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -134,6 +134,25 @@
+
+
+
+
+
+
+
+
+
getController() {
+ return controller;
+ }
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onSelectionChanged() {
+ super.onSelectionChanged();
+ if (menu == null) return;
+ MenuItem item = menu.findItem(R.id.action_contacts_selected);
+ if (item == null) return;
+
+ BaseContactSelectorAdapter a = adapter;
+ selectedContacts = a.getSelectedContactIds();
+
+ int n = selectedContacts.size();
+ int min = 2;
+ boolean enough = n >= min;
+
+ item.setVisible(enough);
+ if (n == 0) {
+ Toast.makeText(getContext(), String.format(getString(R.string.select_at_least_n_contacts), min),
+ Toast.LENGTH_SHORT).show();
+ } else if (n < min) {
+ Toast.makeText(getContext(), String.format(getString(R.string.select_at_least_n_more_contacts), min - n),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java
new file mode 100644
index 000000000..42308599d
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java
@@ -0,0 +1,77 @@
+package org.briarproject.briar.android.socialbackup;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.fragment.app.FragmentTransaction;
+
+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.contactselection.ContactSelectorListener;
+import org.briarproject.briar.android.fragment.BaseFragment;
+import org.briarproject.briar.api.socialbackup.SocialBackupManager;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.inject.Inject;
+
+public class DistributedBackupActivity extends BriarActivity implements
+ BaseFragment.BaseFragmentListener, ContactSelectorListener,
+ ThresholdDefinedListener, ShardsSentDismissedListener {
+
+ private Collection custodians;
+
+ @Inject
+ public SocialBackupManager socialBackupManager;
+
+ @Inject
+ public DatabaseComponent db;
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_distributed_backup);
+ // TODO here we should check if we already have a backup
+ // BackupMetadata backupMetadata = socialBackupManager.getBackupMetadata();
+ // if (backupMetadata == null) {
+ CustodianSelectorFragment fragment =
+ CustodianSelectorFragment.newInstance();
+ // } else {
+ // display the backup metadata
+ showInitialFragment(fragment);
+ }
+
+ @Override
+ public void contactsSelected(Collection contacts) {
+ Toast.makeText(this,
+ String.format("selected %d contacts", contacts.size()),
+ Toast.LENGTH_SHORT).show();
+ custodians = contacts;
+ ThresholdSelectorFragment fragment = ThresholdSelectorFragment.newInstance(contacts.size());
+ showNextFragment(fragment);
+ }
+
+ @Override
+ public void thresholdDefined(int threshold) throws DbException {
+ db.transaction(false, txn -> {
+ socialBackupManager.createBackup(txn, (List) custodians, threshold);
+ ShardsSentFragment fragment = new ShardsSentFragment();
+ showNextFragment(fragment);
+ });
+ }
+
+ @Override
+ public void shardsSentDismissed() {
+ finish();
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/OldDistributedBackupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/OldDistributedBackupActivity.java
new file mode 100644
index 000000000..e74638e3a
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/OldDistributedBackupActivity.java
@@ -0,0 +1,39 @@
+package org.briarproject.briar.android.socialbackup;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.activity.BriarActivity;
+import org.briarproject.briar.android.contactselection.ContactSelectorListener;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import java.util.Collection;
+
+public class OldDistributedBackupActivity extends BriarActivity
+ implements BaseFragment.BaseFragmentListener, ContactSelectorListener {
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_distributed_backup);
+
+// CustodianDisplayFragment fragment =
+// CustodianDisplayFragment.newInstance();
+//
+// showInitialFragment(fragment);
+ }
+
+ @Override
+ public void contactsSelected(Collection contacts) {
+ // do nothing
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ShardsSentDismissedListener.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ShardsSentDismissedListener.java
new file mode 100644
index 000000000..d7a4e7b36
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ShardsSentDismissedListener.java
@@ -0,0 +1,10 @@
+package org.briarproject.briar.android.socialbackup;
+
+import androidx.annotation.UiThread;
+
+public interface ShardsSentDismissedListener {
+
+ @UiThread
+ void shardsSentDismissed();
+
+}
\ No newline at end of file
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ShardsSentFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ShardsSentFragment.java
new file mode 100644
index 000000000..bfac28d3f
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ShardsSentFragment.java
@@ -0,0 +1,65 @@
+package org.briarproject.briar.android.socialbackup;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.fragment.BaseFragment;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+public class ShardsSentFragment extends BaseFragment {
+
+ public static final String TAG = ShardsSentFragment.class.getName();
+
+ protected ShardsSentDismissedListener listener;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requireActivity().setTitle(R.string.title_distributed_backup);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_shards_sent,
+ container, false);
+
+ Button button = view.findViewById(R.id.button);
+ button.setOnClickListener(e -> {
+ listener.shardsSentDismissed();
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ listener = (ShardsSentDismissedListener) context;
+ }
+
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdDefinedListener.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdDefinedListener.java
new file mode 100644
index 000000000..ee99b999a
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdDefinedListener.java
@@ -0,0 +1,12 @@
+package org.briarproject.briar.android.socialbackup;
+
+import org.briarproject.bramble.api.db.DbException;
+
+import androidx.annotation.UiThread;
+
+public interface ThresholdDefinedListener {
+
+ @UiThread
+ void thresholdDefined(int threshold) throws DbException;
+
+}
\ No newline at end of file
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java
new file mode 100644
index 000000000..51c229508
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java
@@ -0,0 +1,169 @@
+package org.briarproject.briar.android.socialbackup;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.ActivityComponent;
+import org.briarproject.briar.android.contactselection.ContactSelectorListener;
+import org.briarproject.briar.android.fragment.BaseFragment;
+import org.magmacollective.darkcrystal.secretsharingwrapper.SecretSharingWrapper;
+
+import static java.util.Objects.requireNonNull;
+
+public class ThresholdSelectorFragment extends BaseFragment {
+
+ public static final String TAG = ThresholdSelectorFragment.class.getName();
+ private static final String NUMBER_CUSTODIANS = "numberCustodians";
+
+ protected ThresholdDefinedListener listener;
+
+ // TODO this should be the actual number of custodians
+ private int numberOfCustodians;
+ private int threshold;
+ private int recommendedThreshold;
+ private SeekBar seekBar;
+ private TextView thresholdRepresentation;
+ private TextView message;
+ private TextView mOfn;
+
+ public static ThresholdSelectorFragment newInstance(int numberCustodians) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(NUMBER_CUSTODIANS, numberCustodians);
+ ThresholdSelectorFragment fragment = new ThresholdSelectorFragment();
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requireActivity().setTitle(R.string.title_define_threshold);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_select_threshold,
+ container, false);
+ Bundle args = requireArguments();
+ numberOfCustodians = args.getInt(NUMBER_CUSTODIANS);
+
+ seekBar = view.findViewById(R.id.seekBar);
+ thresholdRepresentation = view.findViewById(R.id.textViewThresholdRepresentation);
+ message = view.findViewById(R.id.textViewMessage);
+ mOfn = view.findViewById(R.id.textViewmOfn);
+ int max = numberOfCustodians - 3;
+ seekBar.setMax(max);
+ seekBar.setOnSeekBarChangeListener(new SeekBarListener());
+ recommendedThreshold = SecretSharingWrapper.defaultThreshold(numberOfCustodians);
+ threshold = recommendedThreshold;
+ seekBar.setProgress(threshold - 2);
+
+ thresholdRepresentation.setText(buildThresholdRepresentationString());
+ setmOfnText();
+ return view;
+// return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ private void setmOfnText() {
+ mOfn.setText(String.format("%d of %d contacts needed to recover your account", threshold, numberOfCustodians));
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ listener = (ThresholdDefinedListener) context;
+ }
+
+
+ @Override
+ public String getUniqueTag() {
+ return TAG;
+ }
+
+ @Override
+ public void injectFragment(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.define_threshold_actions, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_threshold_defined:
+ try {
+ listener.thresholdDefined(threshold);
+ } catch (DbException e) {
+ e.printStackTrace();
+ }
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private String buildThresholdRepresentationString () {
+ String thresholdRepresentationText = "";
+ for (int i = 0; i < threshold; i++) {
+ thresholdRepresentationText += getString(R.string.filled_bullet);
+ }
+ for (int i = 0; i < (numberOfCustodians - threshold); i++) {
+ thresholdRepresentationText += getString(R.string.linear_bullet);
+ }
+ return thresholdRepresentationText;
+ }
+
+ private class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser) {
+ threshold = progress + 2;
+
+ thresholdRepresentation.setText(
+ buildThresholdRepresentationString()
+ );
+ setmOfnText();
+
+ int sanityLevel = SecretSharingWrapper.thresholdSanity(threshold, numberOfCustodians);
+ int text = R.string.threshold_secure;
+ if (threshold == recommendedThreshold) text = R.string.threshold_recommended;
+ if (sanityLevel < -1) text = R.string.threshold_low_insecure;
+ if (sanityLevel > 0) text = R.string.threshold_high_insecure;
+ message.setText(text);
+ // TODO change colour of thresholdRepresentation to green/red based on sanityLevel
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // do nothing
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // do nothing
+ }
+
+ }
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupController.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupController.java
new file mode 100644
index 000000000..04fa8bc13
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupController.java
@@ -0,0 +1,26 @@
+package org.briarproject.briar.android.socialbackup.creation;
+
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.briar.android.contactselection.ContactSelectorController;
+import org.briarproject.briar.android.contactselection.SelectableContactItem;
+import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
+
+import java.util.Collection;
+
+import androidx.annotation.Nullable;
+
+@NotNullByDefault
+public interface CreateBackupController
+ extends ContactSelectorController {
+
+ void createGroup(String name,
+ ResultExceptionHandler result);
+
+ void sendInvitation(GroupId g, Collection contacts,
+ @Nullable String text,
+ ResultExceptionHandler result);
+
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupControllerImpl.java
new file mode 100644
index 000000000..6614d92a3
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupControllerImpl.java
@@ -0,0 +1,206 @@
+package org.briarproject.briar.android.socialbackup.creation;
+
+import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.crypto.CryptoExecutor;
+import org.briarproject.bramble.api.db.DatabaseExecutor;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.NoSuchContactException;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
+import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.briar.api.identity.AuthorManager;
+import org.briarproject.briar.api.privategroup.GroupMessage;
+import org.briarproject.briar.api.privategroup.GroupMessageFactory;
+import org.briarproject.briar.api.privategroup.PrivateGroup;
+import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
+import org.briarproject.briar.api.privategroup.PrivateGroupManager;
+import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
+import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+
+import androidx.annotation.Nullable;
+
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.LogUtils.logException;
+
+@Immutable
+@NotNullByDefault
+/**
+ * Derived from {@link org.briarproject.briar.android.privategroup.invitation.GroupInvitationControllerImpl}
+ */
+class CreateBackupControllerImpl extends ContactSelectorControllerImpl
+ implements CreateBackupController {
+
+ private static final Logger LOG =
+ Logger.getLogger(
+ CreateBackupControllerImpl.class.getName());
+
+ private final Executor cryptoExecutor;
+ private final ContactManager contactManager;
+ private final IdentityManager identityManager;
+ private final PrivateGroupFactory groupFactory;
+ private final GroupMessageFactory groupMessageFactory;
+ private final PrivateGroupManager groupManager;
+ private final GroupInvitationFactory groupInvitationFactory;
+ private final GroupInvitationManager groupInvitationManager;
+ private final Clock clock;
+
+ @Inject
+ CreateBackupControllerImpl(@DatabaseExecutor Executor dbExecutor,
+ @CryptoExecutor Executor cryptoExecutor,
+ LifecycleManager lifecycleManager, ContactManager contactManager,
+ IdentityManager identityManager, PrivateGroupFactory groupFactory,
+ GroupMessageFactory groupMessageFactory,
+ PrivateGroupManager groupManager,
+ GroupInvitationFactory groupInvitationFactory,
+ GroupInvitationManager groupInvitationManager, Clock clock, AuthorManager authorManager) {
+ super(dbExecutor, lifecycleManager, contactManager, authorManager);
+ this.cryptoExecutor = cryptoExecutor;
+ this.contactManager = contactManager;
+ this.identityManager = identityManager;
+ this.groupFactory = groupFactory;
+ this.groupMessageFactory = groupMessageFactory;
+ this.groupManager = groupManager;
+ this.groupInvitationFactory = groupInvitationFactory;
+ this.groupInvitationManager = groupInvitationManager;
+ this.clock = clock;
+ }
+
+ @Override
+ public void createGroup(String name,
+ ResultExceptionHandler handler) {
+ runOnDbThread(() -> {
+ try {
+ LocalAuthor author = identityManager.getLocalAuthor();
+ createGroupAndMessages(author, name, handler);
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ handler.onException(e);
+ }
+ });
+ }
+
+ private void createGroupAndMessages(LocalAuthor author, String name,
+ ResultExceptionHandler handler) {
+ cryptoExecutor.execute(() -> {
+ LOG.info("Creating group...");
+ PrivateGroup group =
+ groupFactory.createPrivateGroup(name, author);
+ LOG.info("Creating new join announcement...");
+ GroupMessage joinMsg =
+ groupMessageFactory.createJoinMessage(group.getId(),
+ clock.currentTimeMillis(), author);
+ storeGroup(group, joinMsg, handler);
+ });
+ }
+
+ private void storeGroup(PrivateGroup group, GroupMessage joinMsg,
+ ResultExceptionHandler handler) {
+ runOnDbThread(() -> {
+ LOG.info("Adding group to database...");
+ try {
+ groupManager.addPrivateGroup(group, joinMsg, true);
+ handler.onResult(group.getId());
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ handler.onException(e);
+ }
+ });
+ }
+
+ @Override
+ protected boolean isDisabled(GroupId g, Contact c) {
+ return false;
+ }
+
+ @Override
+ public void sendInvitation(GroupId g, Collection contactIds,
+ @Nullable String text,
+ ResultExceptionHandler handler) {
+ runOnDbThread(() -> {
+ try {
+ LocalAuthor localAuthor = identityManager.getLocalAuthor();
+ List contacts = new ArrayList<>();
+ for (ContactId c : contactIds) {
+ try {
+ contacts.add(contactManager.getContact(c));
+ } catch (NoSuchContactException e) {
+ // Continue
+ }
+ }
+ signInvitations(g, localAuthor, contacts, text, handler);
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ handler.onException(e);
+ }
+ });
+ }
+
+ private void signInvitations(GroupId g, LocalAuthor localAuthor,
+ Collection contacts, @Nullable String text,
+ ResultExceptionHandler handler) {
+ cryptoExecutor.execute(() -> {
+ long timestamp = clock.currentTimeMillis();
+ List contexts = new ArrayList<>();
+ for (Contact c : contacts) {
+ byte[] signature = groupInvitationFactory.signInvitation(c, g,
+ timestamp, localAuthor.getPrivateKey());
+ contexts.add(new InvitationContext(c.getId(), timestamp,
+ signature));
+ }
+ sendInvitations(g, contexts, text, handler);
+ });
+ }
+
+ private void sendInvitations(GroupId g,
+ Collection contexts, @Nullable String text,
+ ResultExceptionHandler handler) {
+ runOnDbThread(() -> {
+ try {
+ for (InvitationContext context : contexts) {
+ try {
+ groupInvitationManager.sendInvitation(g,
+ context.contactId, text, context.timestamp,
+ context.signature);
+ } catch (NoSuchContactException e) {
+ // Continue
+ }
+ }
+ //noinspection ConstantConditions
+ handler.onResult(null);
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ handler.onException(e);
+ }
+ });
+ }
+
+ private static class InvitationContext {
+
+ private final ContactId contactId;
+ private final long timestamp;
+ private final byte[] signature;
+
+ private InvitationContext(ContactId contactId, long timestamp,
+ byte[] signature) {
+ this.contactId = contactId;
+ this.timestamp = timestamp;
+ this.signature = signature;
+ }
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupModule.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupModule.java
new file mode 100644
index 000000000..76d64741b
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/creation/CreateBackupModule.java
@@ -0,0 +1,18 @@
+package org.briarproject.briar.android.socialbackup.creation;
+
+import org.briarproject.briar.android.activity.ActivityScope;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class CreateBackupModule {
+
+ @ActivityScope
+ @Provides
+ CreateBackupController provideCreateGroupController(
+ CreateBackupControllerImpl createBackupController) {
+ return createBackupController;
+ }
+
+}
diff --git a/briar-android/src/main/res/drawable/ic_baseline_done_outline_24.xml b/briar-android/src/main/res/drawable/ic_baseline_done_outline_24.xml
new file mode 100644
index 000000000..6601032a8
--- /dev/null
+++ b/briar-android/src/main/res/drawable/ic_baseline_done_outline_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/briar-android/src/main/res/drawable/ic_pie_2_of_3.xml b/briar-android/src/main/res/drawable/ic_pie_2_of_3.xml
new file mode 100644
index 000000000..7f65b7e03
--- /dev/null
+++ b/briar-android/src/main/res/drawable/ic_pie_2_of_3.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/drawable/ic_pie_2_of_5.xml b/briar-android/src/main/res/drawable/ic_pie_2_of_5.xml
new file mode 100644
index 000000000..707dd584d
--- /dev/null
+++ b/briar-android/src/main/res/drawable/ic_pie_2_of_5.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/drawable/ic_pie_3_of_5.xml b/briar-android/src/main/res/drawable/ic_pie_3_of_5.xml
new file mode 100644
index 000000000..dab65ef9f
--- /dev/null
+++ b/briar-android/src/main/res/drawable/ic_pie_3_of_5.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/drawable/ic_pie_4_of_5.xml b/briar-android/src/main/res/drawable/ic_pie_4_of_5.xml
new file mode 100644
index 000000000..3300cd2bf
--- /dev/null
+++ b/briar-android/src/main/res/drawable/ic_pie_4_of_5.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/activity_distributed_backup.xml b/briar-android/src/main/res/layout/activity_distributed_backup.xml
new file mode 100644
index 000000000..9d54ad6f2
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_distributed_backup.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_recovery_custodian1.xml b/briar-android/src/main/res/layout/activity_preview_recovery_custodian1.xml
new file mode 100644
index 000000000..3feffb79a
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_recovery_custodian1.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_recovery_custodian2.xml b/briar-android/src/main/res/layout/activity_preview_recovery_custodian2.xml
new file mode 100644
index 000000000..f8a1cd2bc
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_recovery_custodian2.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_recovery_owner1.xml b/briar-android/src/main/res/layout/activity_preview_recovery_owner1.xml
new file mode 100644
index 000000000..1e8fec49a
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_recovery_owner1.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_recovery_owner2.xml b/briar-android/src/main/res/layout/activity_preview_recovery_owner2.xml
new file mode 100644
index 000000000..9496e994e
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_recovery_owner2.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_recovery_owner3.xml b/briar-android/src/main/res/layout/activity_preview_recovery_owner3.xml
new file mode 100644
index 000000000..57ea4820b
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_recovery_owner3.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_recovery_owner4.xml b/briar-android/src/main/res/layout/activity_preview_recovery_owner4.xml
new file mode 100644
index 000000000..783d045a0
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_recovery_owner4.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_preview_welcome.xml b/briar-android/src/main/res/layout/activity_preview_welcome.xml
new file mode 100644
index 000000000..293518b6b
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_preview_welcome.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/activity_welcome.xml b/briar-android/src/main/res/layout/activity_welcome.xml
new file mode 100644
index 000000000..9c88ce321
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_welcome.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/fragment_account_recovered.xml b/briar-android/src/main/res/layout/fragment_account_recovered.xml
new file mode 100644
index 000000000..fcfa9cfa1
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_account_recovered.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_mockups.xml b/briar-android/src/main/res/layout/fragment_mockups.xml
new file mode 100644
index 000000000..581de435e
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_mockups.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/layout/fragment_recovery_custodian_done.xml b/briar-android/src/main/res/layout/fragment_recovery_custodian_done.xml
new file mode 100644
index 000000000..eb33d3c90
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_custodian_done.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml b/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml
new file mode 100644
index 000000000..4f43bd7a3
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_custodian_error_explainer.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_custodian_explainer.xml b/briar-android/src/main/res/layout/fragment_recovery_custodian_explainer.xml
new file mode 100644
index 000000000..59a183758
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_custodian_explainer.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_error_explainer.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_error_explainer.xml
new file mode 100644
index 000000000..85cf22084
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_error_explainer.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml
new file mode 100644
index 000000000..3e6e8380a
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_explainer.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_main.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_main.xml
new file mode 100644
index 000000000..24b75b598
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_main.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_recovery_owner_recovering.xml b/briar-android/src/main/res/layout/fragment_recovery_owner_recovering.xml
new file mode 100644
index 000000000..00064ebc0
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_recovery_owner_recovering.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_select_threshold.xml b/briar-android/src/main/res/layout/fragment_select_threshold.xml
new file mode 100644
index 000000000..fe74f5cd1
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_select_threshold.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_shards_received.xml b/briar-android/src/main/res/layout/fragment_shards_received.xml
new file mode 100644
index 000000000..cabb2a117
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_shards_received.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_shards_sent.xml b/briar-android/src/main/res/layout/fragment_shards_sent.xml
new file mode 100644
index 000000000..462e3dfcb
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_shards_sent.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/fragment_start.xml b/briar-android/src/main/res/layout/fragment_start.xml
new file mode 100644
index 000000000..d5a212986
--- /dev/null
+++ b/briar-android/src/main/res/layout/fragment_start.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/layout/item_custodian.xml b/briar-android/src/main/res/layout/item_custodian.xml
new file mode 100644
index 000000000..55c95f5e6
--- /dev/null
+++ b/briar-android/src/main/res/layout/item_custodian.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/menu/conversation_actions.xml b/briar-android/src/main/res/menu/conversation_actions.xml
index 8447178c7..da47ae7d4 100644
--- a/briar-android/src/main/res/menu/conversation_actions.xml
+++ b/briar-android/src/main/res/menu/conversation_actions.xml
@@ -27,4 +27,9 @@
android:title="@string/delete_contact"
app:showAsAction="never"/>
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/menu/define_threshold_actions.xml b/briar-android/src/main/res/menu/define_threshold_actions.xml
new file mode 100644
index 000000000..6de6a30d9
--- /dev/null
+++ b/briar-android/src/main/res/menu/define_threshold_actions.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index faa7ae0d9..9db6d4ea0 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -638,4 +638,78 @@
No problem, hope you like it 😀
+
+
+
+
+ Social backup
+ Backup your account using trusted contacts
+
+ Existing Social backup
+ Information about your most recent social backup
+
+
+
+ Select Trusted Contacts:
+
+ Please select at least %d contacts
+ Please select at least %d more contacts
+
+ The minimum number of trusted contacts needed to restore your account:
+ Secure
+ Secure - recommended threshold
+ Insecure – higher threshold recommended
+ Danger of loss – lower threshold recommended
+ Choose Threshold
+
+ Backup pieces sent to trusted contacts
+ Got it
+
+ Backup created 1/6/2020
+
+
+
+ You need to meet your trusted contacts in-person to receive pieces by scanning QR codes
+ Begin
+ Failed to receive backup piece
+ Please check that bluetooth is swtiched on and that no-one but your trusted contact is able to scan the QR code
+ Retry
+ Recovered backup pieces:
+ Scan QR code
+ Recovering account…
+ Account backup piece received
+ Account recovered
+
+
+
+ You need to meet in-person to transfer backup piece
+ Failed to send backup piece
+ Scan code
+ Account backup piece transmitted
+
+
+
+ Social Backup
+ Select Trusted Contacts
+ Choose Threshold
+ Recovery Mode
+ Help recover account
+
+
+
+ Help recover account
+
+
+
+ Create new account
+ Restore account from backup
+
+
+ \u25CF
+ \u25CB
+
+
+ Social Backup
+ Old Social Backup
+
diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml
index 21acdd98f..dfa35a0dd 100644
--- a/briar-android/src/main/res/xml/settings.xml
+++ b/briar-android/src/main/res/xml/settings.xml
@@ -90,6 +90,17 @@
android:layout="@layout/preferences_category"
android:title="@string/security_settings_title">
+
+
+
+
+
+