mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 14:49:53 +01:00
Move backend comms and logic out of KeyAgreementFragment
into ViewModel
This commit is contained in:
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
|
|||||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||||
|
|
||||||
// Accept records with current protocol version, known record type
|
// Accept records with current protocol version, known record type
|
||||||
private static Predicate<Record> ACCEPT = r ->
|
private static final Predicate<Record> ACCEPT = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
isKnownRecordType(r.getRecordType());
|
isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
// Ignore records with current protocol version, unknown record type
|
// Ignore records with current protocol version, unknown record type
|
||||||
private static Predicate<Record> IGNORE = r ->
|
private static final Predicate<Record> IGNORE = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
!isKnownRecordType(r.getRecordType());
|
!isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.briarproject.briar.android.contact.add.nearby;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
abstract class ContactAddingState {
|
||||||
|
|
||||||
|
static class KeyAgreementListening extends ContactAddingState {
|
||||||
|
final Bitmap qrCode;
|
||||||
|
|
||||||
|
KeyAgreementListening(Bitmap qrCode) {
|
||||||
|
this.qrCode = qrCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class QrCodeScanned extends ContactAddingState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeyAgreementWaiting extends ContactAddingState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeyAgreementStarted extends ContactAddingState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ContactExchangeStarted extends ContactAddingState {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ContactExchangeFinished extends ContactAddingState {
|
||||||
|
final ContactExchangeResult result;
|
||||||
|
|
||||||
|
ContactExchangeFinished(ContactExchangeResult result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Failed extends ContactAddingState {
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,14 +7,15 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState;
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.Failed;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.ABORTED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FAILED;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -25,15 +26,33 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
|||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
requireNonNull(getSupportActionBar())
|
requireNonNull(getSupportActionBar())
|
||||||
.setTitle(R.string.add_contact_title);
|
.setTitle(R.string.add_contact_title);
|
||||||
viewModel.getKeyAgreementState()
|
viewModel.getState()
|
||||||
.observe(this, this::onKeyAgreementStateChanged);
|
.observe(this, this::onContactAddingStateChanged);
|
||||||
viewModel.getContactExchangeResult()
|
|
||||||
.observe(this, this::onContactExchangeResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onKeyAgreementStateChanged(KeyAgreementState state) {
|
@Override
|
||||||
if (state == ABORTED || state == FAILED) {
|
public void onBackPressed() {
|
||||||
showErrorFragment();
|
if (viewModel.getState().getValue() instanceof Failed) {
|
||||||
|
// finish this activity when going back in failed state
|
||||||
|
supportFinishAfterTransition();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onContactAddingStateChanged(ContactAddingState state) {
|
||||||
|
if (state instanceof ContactExchangeFinished) {
|
||||||
|
ContactExchangeResult result =
|
||||||
|
((ContactExchangeFinished) state).result;
|
||||||
|
onContactExchangeResult(result);
|
||||||
|
} else if (state instanceof Failed) {
|
||||||
|
// Remove navigation icon, so user can't go back when failed
|
||||||
|
// ErrorFragment will finish or relaunch this activity
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
toolbar.setNavigationIcon(null);
|
||||||
|
|
||||||
|
Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld;
|
||||||
|
onAddingContactFailed(qrCodeTooOld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +79,22 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
|||||||
} else throw new AssertionError();
|
} 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(ContactExchangeErrorFragment.newInstance(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void showErrorFragment() {
|
private void showErrorFragment() {
|
||||||
showNextFragment(new ContactExchangeErrorFragment());
|
showNextFragment(new ContactExchangeErrorFragment());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
package org.briarproject.briar.android.contact.add.nearby;
|
package org.briarproject.briar.android.contact.add.nearby;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
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.connection.ConnectionManager;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
@@ -12,95 +18,208 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
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.KeyAgreementAbortedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
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.KeyAgreementStartedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeFinished;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeStarted;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementListening;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementStarted;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error;
|
import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Error;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success;
|
import org.briarproject.briar.android.contact.add.nearby.ContactExchangeResult.Success;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
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.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.ABORTED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FAILED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FINISHED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.STARTED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.WAITING;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ContactExchangeViewModel extends AndroidViewModel
|
class ContactExchangeViewModel extends AndroidViewModel
|
||||||
implements EventListener {
|
implements EventListener, QrCodeDecoder.ResultCallback {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(ContactExchangeViewModel.class.getName());
|
getLogger(ContactExchangeViewModel.class.getName());
|
||||||
|
|
||||||
enum KeyAgreementState {
|
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
||||||
WAITING, STARTED, FINISHED, ABORTED, FAILED
|
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||||
}
|
|
||||||
|
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final PayloadEncoder payloadEncoder;
|
||||||
|
private final PayloadParser payloadParser;
|
||||||
|
private final Provider<KeyAgreementTask> keyAgreementTaskProvider;
|
||||||
private final ContactExchangeManager contactExchangeManager;
|
private final ContactExchangeManager contactExchangeManager;
|
||||||
private final ConnectionManager connectionManager;
|
private final ConnectionManager connectionManager;
|
||||||
private final MutableLiveData<KeyAgreementState> keyAgreementState =
|
|
||||||
new MutableLiveData<>();
|
private final MutableLiveData<ContactAddingState> state =
|
||||||
private final MutableLiveData<ContactExchangeResult> exchangeResult =
|
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
final QrCodeDecoder qrCodeDecoder;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private KeyAgreementTask task;
|
||||||
|
private volatile boolean gotLocalPayload = false, gotRemotePayload = false;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContactExchangeViewModel(Application app,
|
ContactExchangeViewModel(Application app,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
@IoExecutor Executor ioExecutor,
|
@IoExecutor Executor ioExecutor,
|
||||||
|
PayloadEncoder payloadEncoder,
|
||||||
|
PayloadParser payloadParser,
|
||||||
|
Provider<KeyAgreementTask> keyAgreementTaskProvider,
|
||||||
ContactExchangeManager contactExchangeManager,
|
ContactExchangeManager contactExchangeManager,
|
||||||
ConnectionManager connectionManager) {
|
ConnectionManager connectionManager) {
|
||||||
super(app);
|
super(app);
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.payloadEncoder = payloadEncoder;
|
||||||
|
this.payloadParser = payloadParser;
|
||||||
|
this.keyAgreementTaskProvider = keyAgreementTaskProvider;
|
||||||
this.contactExchangeManager = contactExchangeManager;
|
this.contactExchangeManager = contactExchangeManager;
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
|
qrCodeDecoder = new QrCodeDecoder(ioExecutor, this);
|
||||||
|
eventBus.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCleared() {
|
protected void onCleared() {
|
||||||
super.onCleared();
|
super.onCleared();
|
||||||
eventBus.removeListener(this);
|
eventBus.removeListener(this);
|
||||||
|
stopListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
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
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof KeyAgreementWaitingEvent) {
|
if (e instanceof KeyAgreementListeningEvent) {
|
||||||
keyAgreementState.setValue(WAITING);
|
LOG.info("KeyAgreementListeningEvent received");
|
||||||
|
KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e;
|
||||||
|
onLocalPayloadReceived(event.getLocalPayload());
|
||||||
|
} else if (e instanceof KeyAgreementWaitingEvent) {
|
||||||
|
LOG.info("KeyAgreementWaitingEvent received");
|
||||||
|
state.setValue(new KeyAgreementWaiting());
|
||||||
} else if (e instanceof KeyAgreementStartedEvent) {
|
} else if (e instanceof KeyAgreementStartedEvent) {
|
||||||
keyAgreementState.setValue(STARTED);
|
LOG.info("KeyAgreementStartedEvent received");
|
||||||
} else if (e instanceof KeyAgreementAbortedEvent) {
|
state.setValue(new KeyAgreementStarted());
|
||||||
keyAgreementState.setValue(ABORTED);
|
|
||||||
} else if (e instanceof KeyAgreementFinishedEvent) {
|
} else if (e instanceof KeyAgreementFinishedEvent) {
|
||||||
keyAgreementState.setValue(FINISHED);
|
LOG.info("KeyAgreementFinishedEvent received");
|
||||||
KeyAgreementResult result =
|
KeyAgreementResult result =
|
||||||
((KeyAgreementFinishedEvent) e).getResult();
|
((KeyAgreementFinishedEvent) e).getResult();
|
||||||
startContactExchange(result);
|
startContactExchange(result);
|
||||||
|
state.setValue(new ContactExchangeStarted());
|
||||||
|
} else if (e instanceof KeyAgreementAbortedEvent) {
|
||||||
|
LOG.info("KeyAgreementAbortedEvent received");
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.setValue(new ContactAddingState.Failed());
|
||||||
} else if (e instanceof KeyAgreementFailedEvent) {
|
} else if (e instanceof KeyAgreementFailedEvent) {
|
||||||
keyAgreementState.setValue(FAILED);
|
LOG.info("KeyAgreementFailedEvent received");
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.setValue(new ContactAddingState.Failed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 KeyAgreementListening(qrCode));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onQrCodeDecoded(Result result) {
|
||||||
|
LOG.info("Got result from decoder");
|
||||||
|
// Ignore results until the KeyAgreementTask is ready
|
||||||
|
if (!gotLocalPayload || gotRemotePayload) return;
|
||||||
|
try {
|
||||||
|
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
|
||||||
|
Payload remotePayload = payloadParser.parse(payloadBytes);
|
||||||
|
gotRemotePayload = true;
|
||||||
|
requireNonNull(task).connectAndRunProtocol(remotePayload);
|
||||||
|
state.postValue(new ContactAddingState.QrCodeScanned());
|
||||||
|
} catch (UnsupportedVersionException e) {
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.postValue(new ContactAddingState.Failed(e.isTooOld()));
|
||||||
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
|
LOG.log(WARNING, "QR Code Invalid", e);
|
||||||
|
Toast.makeText(getApplication(), R.string.qr_code_invalid,
|
||||||
|
LENGTH_LONG).show();
|
||||||
|
resetPayloadFlags();
|
||||||
|
state.postValue(new ContactAddingState.Failed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetPayloadFlags() {
|
||||||
|
gotRemotePayload = false;
|
||||||
|
gotLocalPayload = false;
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void startContactExchange(KeyAgreementResult result) {
|
private void startContactExchange(KeyAgreementResult result) {
|
||||||
TransportId t = result.getTransportId();
|
TransportId t = result.getTransportId();
|
||||||
@@ -114,14 +233,17 @@ class ContactExchangeViewModel extends AndroidViewModel
|
|||||||
// Reuse the connection as a transport connection
|
// Reuse the connection as a transport connection
|
||||||
connectionManager
|
connectionManager
|
||||||
.manageOutgoingConnection(contact.getId(), t, conn);
|
.manageOutgoingConnection(contact.getId(), t, conn);
|
||||||
exchangeResult.postValue(new Success(contact.getAuthor()));
|
Success success = new Success(contact.getAuthor());
|
||||||
|
state.postValue(new ContactExchangeFinished(success));
|
||||||
} catch (ContactExistsException e) {
|
} catch (ContactExistsException e) {
|
||||||
tryToClose(conn);
|
tryToClose(conn);
|
||||||
exchangeResult.postValue(new Error(e.getRemoteAuthor()));
|
Error error = new Error(e.getRemoteAuthor());
|
||||||
|
state.postValue(new ContactExchangeFinished(error));
|
||||||
} catch (DbException | IOException e) {
|
} catch (DbException | IOException e) {
|
||||||
tryToClose(conn);
|
tryToClose(conn);
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
exchangeResult.postValue(new Error(null));
|
Error error = new Error(null);
|
||||||
|
state.postValue(new ContactExchangeFinished(error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -135,11 +257,8 @@ class ContactExchangeViewModel extends AndroidViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<KeyAgreementState> getKeyAgreementState() {
|
LiveData<ContactAddingState> getState() {
|
||||||
return keyAgreementState;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<ContactExchangeResult> getContactExchangeResult() {
|
|
||||||
return exchangeResult;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
||||||
if (isWifiReady() && isBluetoothReady()) {
|
if (isWifiReady() && isBluetoothReady()) {
|
||||||
LOG.info("Wifi and Bluetooth are ready");
|
LOG.info("Wifi and Bluetooth are ready");
|
||||||
|
viewModel.startListening();
|
||||||
showQrCodeFragment();
|
showQrCodeFragment();
|
||||||
} else {
|
} else {
|
||||||
if (shouldEnableWifi()) {
|
if (shouldEnableWifi()) {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package org.briarproject.briar.android.contact.add.nearby;
|
package org.briarproject.briar.android.contact.add.nearby;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -12,36 +10,24 @@ import android.widget.LinearLayout.LayoutParams;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.zxing.Result;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
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.KeyAgreementListeningEvent;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState;
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.ContactExchangeStarted;
|
||||||
import org.briarproject.briar.android.fragment.BaseEventFragment;
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.Failed;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementStarted;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.KeyAgreementWaiting;
|
||||||
|
import org.briarproject.briar.android.contact.add.nearby.ContactAddingState.QrCodeScanned;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.view.QrCodeView;
|
import org.briarproject.briar.android.view.QrCodeView;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||||
@@ -50,39 +36,20 @@ import static android.view.View.VISIBLE;
|
|||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
import static android.widget.LinearLayout.HORIZONTAL;
|
import static android.widget.LinearLayout.HORIZONTAL;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.ABORTED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FAILED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.FINISHED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.STARTED;
|
|
||||||
import static org.briarproject.briar.android.contact.add.nearby.ContactExchangeViewModel.KeyAgreementState.WAITING;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class KeyAgreementFragment extends BaseEventFragment
|
public class KeyAgreementFragment extends BaseFragment
|
||||||
implements QrCodeDecoder.ResultCallback, QrCodeView.FullscreenListener {
|
implements QrCodeView.FullscreenListener {
|
||||||
|
|
||||||
static final String TAG = KeyAgreementFragment.class.getName();
|
static final String TAG = KeyAgreementFragment.class.getName();
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
private static final Logger LOG = Logger.getLogger(TAG);
|
||||||
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
|
||||||
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@Inject
|
|
||||||
Provider<KeyAgreementTask> keyAgreementTaskProvider;
|
|
||||||
@Inject
|
|
||||||
PayloadEncoder payloadEncoder;
|
|
||||||
@Inject
|
|
||||||
PayloadParser payloadParser;
|
|
||||||
@Inject
|
|
||||||
@IoExecutor
|
|
||||||
Executor ioExecutor;
|
|
||||||
@Inject
|
|
||||||
EventBus eventBus;
|
|
||||||
|
|
||||||
private ContactExchangeViewModel viewModel;
|
private ContactExchangeViewModel viewModel;
|
||||||
private CameraView cameraView;
|
private CameraView cameraView;
|
||||||
@@ -91,10 +58,6 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
private QrCodeView qrCodeView;
|
private QrCodeView qrCodeView;
|
||||||
private TextView status;
|
private TextView status;
|
||||||
|
|
||||||
private boolean gotRemotePayload;
|
|
||||||
private volatile boolean gotLocalPayload;
|
|
||||||
private KeyAgreementTask task;
|
|
||||||
|
|
||||||
public static KeyAgreementFragment newInstance() {
|
public static KeyAgreementFragment newInstance() {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
KeyAgreementFragment fragment = new KeyAgreementFragment();
|
KeyAgreementFragment fragment = new KeyAgreementFragment();
|
||||||
@@ -109,11 +72,6 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
.get(ContactExchangeViewModel.class);
|
.get(ContactExchangeViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUniqueTag() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
@@ -133,16 +91,15 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
qrCodeView = view.findViewById(R.id.qr_code_view);
|
qrCodeView = view.findViewById(R.id.qr_code_view);
|
||||||
qrCodeView.setFullscreenListener(this);
|
qrCodeView.setFullscreenListener(this);
|
||||||
|
|
||||||
LifecycleOwner lifecycleOwner = getViewLifecycleOwner();
|
viewModel.getState().observe(getViewLifecycleOwner(),
|
||||||
viewModel.getKeyAgreementState()
|
this::onContactAddingStateChanged);
|
||||||
.observe(lifecycleOwner, this::onKeyAgreementStateChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||||
cameraView.setPreviewConsumer(new QrCodeDecoder(this));
|
cameraView.setPreviewConsumer(viewModel.qrCodeDecoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,7 +110,16 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
} catch (CameraException e) {
|
} catch (CameraException e) {
|
||||||
logCameraExceptionAndFinish(e);
|
logCameraExceptionAndFinish(e);
|
||||||
}
|
}
|
||||||
startListening();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
try {
|
||||||
|
cameraView.stop();
|
||||||
|
} catch (CameraException e) {
|
||||||
|
logCameraExceptionAndFinish(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -178,17 +144,42 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
cameraOverlay.invalidate();
|
cameraOverlay.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@UiThread
|
||||||
public void onStop() {
|
private void onContactAddingStateChanged(ContactAddingState state) {
|
||||||
super.onStop();
|
if (state instanceof ContactAddingState.KeyAgreementListening) {
|
||||||
stopListening();
|
Bitmap qrCode =
|
||||||
try {
|
((ContactAddingState.KeyAgreementListening) state).qrCode;
|
||||||
cameraView.stop();
|
qrCodeView.setQrCode(qrCode);
|
||||||
} catch (CameraException e) {
|
} else if (state instanceof QrCodeScanned) {
|
||||||
logCameraExceptionAndFinish(e);
|
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 KeyAgreementWaiting) {
|
||||||
|
status.setText(R.string.waiting_for_contact_to_scan);
|
||||||
|
} else if (state instanceof KeyAgreementStarted) {
|
||||||
|
qrCodeView.setVisibility(INVISIBLE);
|
||||||
|
statusView.setVisibility(VISIBLE);
|
||||||
|
status.setText(R.string.authenticating_with_device);
|
||||||
|
} else if (state instanceof ContactExchangeStarted) {
|
||||||
|
statusView.setVisibility(VISIBLE);
|
||||||
|
status.setText(R.string.exchanging_contact_details);
|
||||||
|
} else if (state instanceof Failed) {
|
||||||
|
// the activity will replace this fragment with an error fragment
|
||||||
|
statusView.setVisibility(INVISIBLE);
|
||||||
|
cameraView.setVisibility(INVISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void logCameraExceptionAndFinish(CameraException e) {
|
private void logCameraExceptionAndFinish(CameraException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
@@ -197,130 +188,6 @@ public class KeyAgreementFragment extends BaseEventFragment
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void reset() {
|
|
||||||
// If we've stopped the camera view, restart it
|
|
||||||
if (gotRemotePayload) {
|
|
||||||
try {
|
|
||||||
cameraView.start();
|
|
||||||
} catch (CameraException e) {
|
|
||||||
logCameraExceptionAndFinish(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statusView.setVisibility(INVISIBLE);
|
|
||||||
cameraView.setVisibility(VISIBLE);
|
|
||||||
gotRemotePayload = false;
|
|
||||||
gotLocalPayload = false;
|
|
||||||
startListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void qrCodeScanned(String content) {
|
|
||||||
try {
|
|
||||||
byte[] payloadBytes = content.getBytes(ISO_8859_1);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
|
|
||||||
Payload remotePayload = payloadParser.parse(payloadBytes);
|
|
||||||
gotRemotePayload = true;
|
|
||||||
cameraView.stop();
|
|
||||||
cameraView.setVisibility(INVISIBLE);
|
|
||||||
statusView.setVisibility(VISIBLE);
|
|
||||||
status.setText(R.string.connecting_to_device);
|
|
||||||
task.connectAndRunProtocol(remotePayload);
|
|
||||||
} catch (UnsupportedVersionException e) {
|
|
||||||
reset();
|
|
||||||
String msg;
|
|
||||||
if (e.isTooOld()) {
|
|
||||||
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(ContactExchangeErrorFragment.newInstance(msg));
|
|
||||||
} catch (CameraException e) {
|
|
||||||
logCameraExceptionAndFinish(e);
|
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
LOG.log(WARNING, "QR Code Invalid", e);
|
|
||||||
reset();
|
|
||||||
Toast.makeText(getActivity(), R.string.qr_code_invalid,
|
|
||||||
LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof KeyAgreementListeningEvent) {
|
|
||||||
KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e;
|
|
||||||
gotLocalPayload = true;
|
|
||||||
setQrCode(event.getLocalPayload());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
private void onKeyAgreementStateChanged(KeyAgreementState state) {
|
|
||||||
if (state == WAITING) {
|
|
||||||
status.setText(R.string.waiting_for_contact_to_scan);
|
|
||||||
} else if (state == STARTED) {
|
|
||||||
qrCodeView.setVisibility(INVISIBLE);
|
|
||||||
statusView.setVisibility(VISIBLE);
|
|
||||||
status.setText(R.string.authenticating_with_device);
|
|
||||||
} else if (state == FINISHED) {
|
|
||||||
statusView.setVisibility(VISIBLE);
|
|
||||||
status.setText(R.string.exchanging_contact_details);
|
|
||||||
} else if (state == ABORTED || state == FAILED) {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setQrCode(Payload localPayload) {
|
|
||||||
Context context = getContext();
|
|
||||||
if (context == null) return;
|
|
||||||
DisplayMetrics dm = context.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);
|
|
||||||
runOnUiThreadUnlessDestroyed(
|
|
||||||
() -> qrCodeView.setQrCode(qrCode));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleResult(Result result) {
|
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
|
||||||
LOG.info("Got result from decoder");
|
|
||||||
// Ignore results until the KeyAgreementTask is ready
|
|
||||||
if (!gotLocalPayload) return;
|
|
||||||
if (!gotRemotePayload) qrCodeScanned(result.getText());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finish() {
|
protected void finish() {
|
||||||
requireActivity().getSupportFragmentManager().popBackStack();
|
requireActivity().getSupportFragmentManager().popBackStack();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.hardware.Camera;
|
|||||||
import android.hardware.Camera.CameraInfo;
|
import android.hardware.Camera.CameraInfo;
|
||||||
import android.hardware.Camera.PreviewCallback;
|
import android.hardware.Camera.PreviewCallback;
|
||||||
import android.hardware.Camera.Size;
|
import android.hardware.Camera.Size;
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.BinaryBitmap;
|
||||||
import com.google.zxing.LuminanceSource;
|
import com.google.zxing.LuminanceSource;
|
||||||
@@ -15,10 +14,12 @@ import com.google.zxing.Result;
|
|||||||
import com.google.zxing.common.HybridBinarizer;
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
import com.google.zxing.qrcode.QRCodeReader;
|
import com.google.zxing.qrcode.QRCodeReader;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
@@ -26,22 +27,23 @@ import androidx.annotation.UiThread;
|
|||||||
import static com.google.zxing.DecodeHintType.CHARACTER_SET;
|
import static com.google.zxing.DecodeHintType.CHARACTER_SET;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG = getLogger(QrCodeDecoder.class.getName());
|
||||||
Logger.getLogger(QrCodeDecoder.class.getName());
|
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
private final Reader reader = new QRCodeReader();
|
private final Reader reader = new QRCodeReader();
|
||||||
private final ResultCallback callback;
|
private final ResultCallback callback;
|
||||||
|
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
private int cameraIndex = 0;
|
private int cameraIndex = 0;
|
||||||
|
|
||||||
QrCodeDecoder(ResultCallback callback) {
|
QrCodeDecoder(@IoExecutor Executor ioExecutor, ResultCallback callback) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +76,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
if (data.length == size.width * size.height * 3 / 2) {
|
if (data.length == size.width * size.height * 3 / 2) {
|
||||||
CameraInfo info = new CameraInfo();
|
CameraInfo info = new CameraInfo();
|
||||||
Camera.getCameraInfo(cameraIndex, info);
|
Camera.getCameraInfo(cameraIndex, info);
|
||||||
new DecoderTask(data, size.width, size.height,
|
decode(data, size.width, size.height, info.orientation);
|
||||||
info.orientation).execute();
|
|
||||||
} else {
|
} else {
|
||||||
// Camera parameters have changed - ask for a new preview
|
// Camera parameters have changed - ask for a new preview
|
||||||
LOG.info("Preview size does not match camera parameters");
|
LOG.info("Preview size does not match camera parameters");
|
||||||
@@ -89,43 +90,23 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DecoderTask extends AsyncTask<Void, Void, Void> {
|
private void decode(byte[] data, int width, int height, int orientation) {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
private final byte[] data;
|
|
||||||
private final int width, height, orientation;
|
|
||||||
|
|
||||||
private DecoderTask(byte[] data, int width, int height,
|
|
||||||
int orientation) {
|
|
||||||
this.data = data;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.orientation = orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
BinaryBitmap bitmap = binarize(data, width, height, orientation);
|
BinaryBitmap bitmap = binarize(data, width, height, orientation);
|
||||||
Result result;
|
Result result;
|
||||||
try {
|
try {
|
||||||
result = reader.decode(bitmap,
|
result = reader.decode(bitmap,
|
||||||
singletonMap(CHARACTER_SET, "ISO8859_1"));
|
singletonMap(CHARACTER_SET, "ISO8859_1"));
|
||||||
|
callback.onQrCodeDecoded(result);
|
||||||
} catch (ReaderException e) {
|
} catch (ReaderException e) {
|
||||||
// No barcode found
|
// No barcode found
|
||||||
return null;
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.warning("Invalid preview frame");
|
LOG.warning("Invalid preview frame");
|
||||||
return null;
|
|
||||||
} finally {
|
} finally {
|
||||||
reader.reset();
|
reader.reset();
|
||||||
}
|
}
|
||||||
callback.handleResult(result);
|
});
|
||||||
return null;
|
askForPreviewFrame();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void result) {
|
|
||||||
askForPreviewFrame();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BinaryBitmap binarize(byte[] data, int width, int height,
|
private static BinaryBitmap binarize(byte[] data, int width, int height,
|
||||||
@@ -143,7 +124,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ResultCallback {
|
interface ResultCallback {
|
||||||
|
@IoExecutor
|
||||||
void handleResult(Result result);
|
void onQrCodeDecoded(Result result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user