diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ExistingBackupFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ExistingBackupFragment.java index 7b3cd6300..7962f04d1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ExistingBackupFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ExistingBackupFragment.java @@ -2,32 +2,37 @@ package org.briarproject.briar.android.socialbackup; import android.content.Context; import android.os.Bundle; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ImageSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.identity.Author; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.contact.ContactListAdapter; +import org.briarproject.briar.android.contact.ContactListItem; +import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.fragment.BaseFragment; -import org.briarproject.briar.api.socialbackup.BackupMetadata; +import org.briarproject.briar.android.view.BriarRecyclerView; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; -public class ExistingBackupFragment extends BaseFragment { +public class ExistingBackupFragment extends BaseFragment implements + OnContactClickListener { - private static final String THRESHOLD = "threshold"; - private static final String CUSTODIANS = "custodians"; public static final String TAG = ExistingBackupFragment.class.getName(); + private final ContactListAdapter adapter = new ContactListAdapter(this); + private BriarRecyclerView list; @Inject ViewModelProvider.Factory viewModelFactory; @@ -52,32 +57,41 @@ public class ExistingBackupFragment extends BaseFragment { @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + // change toolbar text (relevant when navigating back to this fragment) +// requireActivity().setTitle(R.string.social_backup_trusted_contacts); + View view = inflater.inflate(R.layout.fragment_existing_backup, container, false); - BackupMetadata backupMetadata = viewModel.getBackupMetadata(); - List custodians = backupMetadata.getCustodians(); + viewModel.loadCustodianList(); + list = view.findViewById(R.id.custodianList); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + list.setAdapter(adapter); + list.setEmptyText(R.string.no_contacts); - StringBuilder custodianNamesString = new StringBuilder(); - for (Author custodian : custodians) { - custodianNamesString - .append("• ") - .append(custodian.getName()) - .append("\n"); - } + viewModel.getContactListItems().observe(getViewLifecycleOwner(), + result -> result.onError(this::handleException) + .onSuccess(adapter::submitList) + ); - TextView textViewThreshold = view.findViewById(R.id.textViewThreshold); - textViewThreshold.setText(getString(R.string.existing_backup_explain, - backupMetadata.getThreshold())); - TextView textViewCustodians = - view.findViewById(R.id.textViewCustodians); - textViewCustodians.setText(custodianNamesString); + int threshold = viewModel.getThresholdFromExistingBackup(); + int numberOfCustodians = + viewModel.getNumberOfCustodiansFromExistingBackup(); + + TextView mOfn = view.findViewById(R.id.textViewThreshold); + mOfn.setText(String.format( + getString(R.string.existing_backup_explain), threshold)); + + TextView thresholdRepresentation = + view.findViewById(R.id.textViewThresholdRepresentation); + thresholdRepresentation.setText( + buildThresholdRepresentationString(threshold, + numberOfCustodians)); return view; } @Override public void onAttach(Context context) { super.onAttach(context); -// listener = (ShardsSentDismissedListener) context; } @Override @@ -85,4 +99,27 @@ public class ExistingBackupFragment extends BaseFragment { return TAG; } + @Override + public void onItemClick(View view, ContactListItem item) { + } + + private SpannableStringBuilder buildThresholdRepresentationString( + int threshold, int numberOfCustodians) { + char[] charArray = new char[numberOfCustodians]; + Arrays.fill(charArray, ' '); + SpannableStringBuilder string = + new SpannableStringBuilder(new String(charArray)); + + for (int i = 0; i < numberOfCustodians; i++) { + int drawable = i < threshold + ? R.drawable.ic_custodian_required + : R.drawable.ic_custodian_optional; + string.setSpan(new ImageSpan(getContext(), drawable), i, + i + 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + // If we have more than 6, split it on two lines + if (numberOfCustodians > 6) string.insert(4, "\n"); + return string; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupActivity.java index 0c616cb88..134fc0f8f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupActivity.java @@ -12,6 +12,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorListener; import org.briarproject.briar.android.fragment.BaseFragment; import java.util.Collection; +import java.util.List; import javax.inject.Inject; @@ -84,7 +85,7 @@ public class SocialBackupSetupActivity extends BriarActivity implements Toast.makeText(this, String.format("Selected %d contacts", contacts.size()), Toast.LENGTH_SHORT).show(); - viewModel.setCustodians(contacts); + viewModel.setCustodians((List) contacts); ThresholdSelectorFragment fragment = ThresholdSelectorFragment.newInstance(contacts.size()); showNextFragment(fragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupViewModel.java index 1b80dfa2e..b9050d4e5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/SocialBackupSetupViewModel.java @@ -2,29 +2,37 @@ package org.briarproject.briar.android.socialbackup; import android.app.Application; +import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.briar.android.contact.ContactsViewModel; +import org.briarproject.briar.api.conversation.ConversationManager; +import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.socialbackup.BackupMetadata; import org.briarproject.briar.api.socialbackup.SocialBackupManager; -import java.util.Collection; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.MutableLiveData; -public class SocialBackupSetupViewModel extends AndroidViewModel { +public class SocialBackupSetupViewModel extends ContactsViewModel { private final SocialBackupManager socialBackupManager; private final DatabaseComponent db; private final ContactManager contactManager; private BackupMetadata backupMetadata; - private Collection custodians; + private List custodians; private int threshold; @@ -38,20 +46,39 @@ public class SocialBackupSetupViewModel extends AndroidViewModel { private final MutableLiveData state = new MutableLiveData<>(); - @Inject public SocialBackupSetupViewModel( @NonNull Application app, DatabaseComponent db, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + AuthorManager authorManager, + ConversationManager conversationManager, + ConnectionRegistry connectionRegistry, + EventBus eventBus, + AndroidExecutor androidExecutor, SocialBackupManager socialBackupManager, ContactManager contactManager ) { - super(app); + super(app, dbExecutor, lifecycleManager, db, androidExecutor, + contactManager, authorManager, conversationManager, + connectionRegistry, eventBus); + this.socialBackupManager = socialBackupManager; this.db = db; this.contactManager = contactManager; } + public void loadCustodianList() { + try { + custodians = db.transactionWithResult(true, + socialBackupManager::getCustodianContactIds); + } catch (DbException e) { + custodians = new ArrayList<>(); + } + loadContacts(); + } + public boolean haveExistingBackup() { try { backupMetadata = db.transactionWithNullableResult(true, @@ -62,15 +89,15 @@ public class SocialBackupSetupViewModel extends AndroidViewModel { return (backupMetadata != null); } - public BackupMetadata getBackupMetadata() { - return backupMetadata; - } +// public BackupMetadata getBackupMetadata() { +// return backupMetadata; +// } public boolean haveEnoughContacts() throws DbException { return (contactManager.getContacts().size() > 1); } - public void setCustodians(Collection contacts) { + public void setCustodians(List contacts) { custodians = contacts; } @@ -102,5 +129,19 @@ public class SocialBackupSetupViewModel extends AndroidViewModel { public void onExplainerDismissed() { state.postValue(State.CHOOSING_CUSTODIANS); } + + @Override + protected boolean displayContact(ContactId contactId) { + // Check if contact holds a backup piece + return custodians.contains(contactId); + } + + public int getThresholdFromExistingBackup() { + return backupMetadata.getThreshold(); + } + + public int getNumberOfCustodiansFromExistingBackup() { + return backupMetadata.getCustodians().size(); + } } 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 index b61126d8a..c124c8227 100644 --- 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 @@ -123,7 +123,6 @@ public class ThresholdSelectorFragment extends BaseFragment { super.onAttach(context); } - @Override public String getUniqueTag() { return TAG; diff --git a/briar-android/src/main/res/layout/fragment_existing_backup.xml b/briar-android/src/main/res/layout/fragment_existing_backup.xml index 4b4c79e81..68efc37eb 100644 --- a/briar-android/src/main/res/layout/fragment_existing_backup.xml +++ b/briar-android/src/main/res/layout/fragment_existing_backup.xml @@ -1,7 +1,6 @@ + + app:layout_constraintTop_toBottomOf="@+id/textViewThresholdRepresentation" /> + + + \ 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 64718b6dc..32e8ea053 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -650,18 +650,19 @@ Information about your most recent social backup - Select Trusted Contacts: + + Select Trusted Contacts Please select at least %d contacts Please select at least %d more contacts Too many! Please select no more than %d contacts - Choose the minimum number of trusted contacts needed to restore your account + Set the minimum number of trusted contacts needed Two trusted contacts will be needed to restore your account Secure Secure - recommended threshold Insecure – higher threshold recommended Danger of loss – lower threshold recommended - Choose Threshold + Choose Minimum Needed %d of %d contacts needed to recover your account @@ -702,7 +703,7 @@ Social Backup Select Trusted Contacts - Threshold + Minimum Needed Recovery Mode Help recover account @@ -713,11 +714,9 @@ Create new account Restore account from backup - - \u25CF - \u25CB + Social Backup Restore Account @@ -728,11 +727,15 @@ Create new account or recover existing account Recover Account Help recover account - %d of the following contacts are needed to restore your account: + Any %d of the following contacts are needed to restore your Briar account, should you lose access to it. To make a social backup, you need at least 2 contacts in your contacts list There was an error reading your contacts list + + + Trusted Contacts + @@ -780,4 +783,5 @@ Briar data has been wiped from %1$s\'s device. They will be unreachable until they recover their account. Ok + diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java index f8f4522ac..d3fa8e486 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java @@ -78,4 +78,11 @@ public interface SocialBackupManager extends */ byte[] getReturnShardPayloadBytes(Transaction txn, ContactId contactId) throws DbException; + + + /** + * Get a list of the contact ids of your custodians, or an empty + * list if no backup exists. + */ + List getCustodianContactIds(Transaction txn); } diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java index c4cfb09d1..31585175a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java @@ -613,4 +613,30 @@ class SocialBackupManagerImpl extends ConversationClientImpl results.entrySet().iterator().next(); return new Pair<>(e.getKey(), e.getValue()); } + + public List getCustodianContactIds(Transaction txn) { + ArrayList contactIds = new ArrayList<>(); + try { + BackupMetadata b = getBackupMetadata(txn); + if (b == null) throw new DbException(); + List custodians = b.getCustodians(); + for (Author custodian : custodians) { + contactIds.add(authorToContactId(txn, custodian)); + } + } catch (DbException ignored) { + // Will return an empty list + } + return contactIds; + } + + private ContactId authorToContactId(Transaction txn, Author author) + throws DbException { + ArrayList contacts = + (ArrayList) contactManager.getContacts(txn); + for (Contact c : contacts) { + if (c.getAuthor().equals(author)) return c.getId(); + } + throw new DbException(); + } + }