diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index de07b1173..7596c8532 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -156,22 +156,13 @@ android:value="org.briarproject.briar.android.settings.SettingsActivity" /> - - - - + android:label="@string/activity_name_custodian_help_recovery" + android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"> + android:value="org.briarproject.briar.android.conversation.ConversationActivity" /> - - - { - 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(); - } - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/CustodianRecoveryModeExplainerFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianRecoveryModeExplainerFragment.java similarity index 91% rename from briar-android/src/main/java/org/briarproject/briar/android/socialbackup/CustodianRecoveryModeExplainerFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianRecoveryModeExplainerFragment.java index c682c1d96..49df13ebf 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/CustodianRecoveryModeExplainerFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianRecoveryModeExplainerFragment.java @@ -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; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java index 1f50328dc..bd95f3260 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java @@ -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(); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java index 4142d6d47..f2614f42d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java @@ -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 showCameraFragment = @@ -52,8 +54,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel new MutableLiveEvent<>(); private final MutableLiveData 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) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeExplainerFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeExplainerFragment.java index cf047012e..00da94a01 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeExplainerFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeExplainerFragment.java @@ -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; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/OwnerRecoveryModeMainFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeMainFragment.java similarity index 53% rename from briar-android/src/main/java/org/briarproject/briar/android/socialbackup/OwnerRecoveryModeMainFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeMainFragment.java index 3191126ee..aeeb4eac0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/OwnerRecoveryModeMainFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerRecoveryModeMainFragment.java @@ -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; - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java index c963ca564..2cb02f3a4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java @@ -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, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java index ea8f5b9dd..85881fe5b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java @@ -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 state = new MutableLiveData<>(); + private final MutableLiveEvent startClicked = + new MutableLiveEvent<>(); private boolean wasContinueClicked = false; private boolean isActivityResumed = false; + private ArrayList 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 getStartClicked() { + return startClicked; + } + LiveData 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; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RecoverActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RecoverActivity.java deleted file mode 100644 index e76d38efb..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RecoverActivity.java +++ /dev/null @@ -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."); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardActivity.java deleted file mode 100644 index 975c4bf83..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardActivity.java +++ /dev/null @@ -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 permissionLauncher = - registerForActivityResult( - new ActivityResultContracts.RequestMultiplePermissions(), - r -> - permissionManager.onRequestPermissionResult(r, - viewModel::showQrCodeFragmentIfAllowed)); - private final ActivityResultLauncher bluetoothLauncher = - registerForActivityResult(new RequestBluetoothDiscoverable(), - this::onBluetoothDiscoverableResult); - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - viewModel = new ViewModelProvider(this, viewModelFactory) - .get(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."); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardFragment.java deleted file mode 100644 index c92de05ea..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardFragment.java +++ /dev/null @@ -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(); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardState.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardState.java deleted file mode 100644 index ea9b1fa9f..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardState.java +++ /dev/null @@ -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 - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardViewModel.java deleted file mode 100644 index 3e4576d8e..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/ReturnShardViewModel.java +++ /dev/null @@ -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 keyAgreementTaskProvider; - private final SocialBackupExchangeManager socialBackupExchangeManager; - private final SocialBackupManager socialBackupManager; - private final ConnectionManager connectionManager; - - private final MutableLiveEvent checkPermissions = - new MutableLiveEvent<>(); - private final MutableLiveEvent requestBluetoothDiscoverable = - new MutableLiveEvent<>(); - private final MutableLiveEvent showQrCodeFragment = - new MutableLiveEvent<>(); - private final MutableLiveData state = - new MutableLiveData<>(); - - final QrCodeDecoder qrCodeDecoder; - final BroadcastReceiver - bluetoothReceiver = new BluetoothStateReceiver(); - - @Nullable - private final BluetoothAdapter bt; - @Nullable - private final Plugin wifiPlugin, bluetoothPlugin; - - // UiThread - private BluetoothDecision - bluetoothDecision = BluetoothDecision.UNKNOWN; - - private boolean wasContinueClicked = false; - private boolean isActivityResumed = false; - - /** - * Records whether we've enabled the wifi plugin so we don't enable it more - * than once. - */ - private boolean hasEnabledWifi = false; - - /** - * Records whether we've enabled the Bluetooth plugin so we don't enable it - * more than once. - */ - private boolean hasEnabledBluetooth = false; - - @Nullable - private KeyAgreementTask task; - private volatile boolean gotLocalPayload = false, gotRemotePayload = false; - - @Inject - ReturnShardViewModel(Application app, - EventBus eventBus, - AndroidExecutor androidExecutor, - @IoExecutor Executor ioExecutor, - PluginManager pluginManager, - PayloadEncoder payloadEncoder, - PayloadParser payloadParser, - Provider 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 getCheckPermissions() { - return checkPermissions; - } - - LiveEvent getRequestBluetoothDiscoverable() { - return requestBluetoothDiscoverable; - } - - LiveEvent getShowQrCodeFragment() { - return showQrCodeFragment; - } - - LiveData getState() { - return state; - } - - public void setSending(boolean sending) { - this.sending = sending; - } - - public void setReturnShardPayload(ReturnShardPayload returnShardPayload) { - this.returnShardPayload = returnShardPayload; - } - - QrCodeDecoder getQrCodeDecoder() { - return qrCodeDecoder; - } -} diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 6009c363b..b13b2de09 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -675,13 +675,13 @@ - You need to meet your trusted contacts in-person to receive pieces by scanning QR codes + You need to meet your trusted contacts in-person to receive pieces 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 + Show QR code Recovering account… Account backup piece received Account recovered diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index d7ae38e4a..dfa35a0dd 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -96,7 +96,7 @@ app:iconSpaceReserved="false">