diff --git a/briar-android/build.gradle b/briar-android/build.gradle index d9e68377505e960730090713fd49e5ccff969076..ea742e84e7d409a16364dfc5a5e698786b8dd08f 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -60,6 +60,7 @@ dependencies { androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.0.2" androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' androidTestImplementation 'junit:junit:4.12' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } def getStdout = { command, defaultValue -> diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index be1d305ff9e0e0daf9587d61a881096d4248d8da..83630d50c1ad07e899f68b7f9324cc9f44c8e653 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -328,7 +328,7 @@ </activity> <activity - android:name="org.briarproject.briar.android.keyagreement.MailboxExchangeActivity" + android:name="org.briarproject.briar.android.keyagreement.mailbox.MailboxKeyAgreementActivity" android:label="@string/mailbox_add" android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity" android:theme="@style/BriarTheme.NoActionBar"> diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 7b369e53686f4f9be7fbf59ecd4a8758d98c4638..7ab6fe90310aae4f6c55f155d5e34b51e3744a22 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -31,7 +31,10 @@ import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment; import org.briarproject.briar.android.keyagreement.IntroFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; -import org.briarproject.briar.android.keyagreement.MailboxExchangeActivity; +import org.briarproject.briar.android.keyagreement.mailbox.ExchangeFragment; +import org.briarproject.briar.android.keyagreement.mailbox.MailboxKeyAgreementActivity; +import org.briarproject.briar.android.keyagreement.mailbox.ScanQrCodeFragment; +import org.briarproject.briar.android.keyagreement.mailbox.ShowQrCodeFragment; import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.DozeFragment; @@ -108,8 +111,6 @@ public interface ActivityComponent { void inject(ContactExchangeActivity activity); - void inject(MailboxExchangeActivity activity); - void inject(KeyAgreementActivity activity); void inject(ConversationActivity activity); @@ -220,4 +221,12 @@ public interface ActivityComponent { void inject(ContactExchangeErrorFragment fragment); void inject(MailboxFragment mailboxFragment); + + void inject(MailboxKeyAgreementActivity mailboxKeyAgreementActivity); + + void inject(ScanQrCodeFragment scanQrCodeFragment); + + void inject(ShowQrCodeFragment showQrCodeFragment); + + void inject(ExchangeFragment exchangeFragment); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java index b21d072321464b72d3e20d4123f46b12235651d3..775cd5e6440e83e5be1452ac56af010e4b2796c0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java @@ -6,6 +6,8 @@ import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbControllerImpl; +import org.briarproject.briar.android.keyagreement.mailbox.KeyAgreementController; +import org.briarproject.briar.android.keyagreement.mailbox.KeyAgreementControllerImpl; import org.briarproject.briar.android.login.PasswordController; import org.briarproject.briar.android.login.PasswordControllerImpl; import org.briarproject.briar.android.login.SetupController; @@ -46,6 +48,13 @@ public class ActivityModule { return setupController; } + @ActivityScope + @Provides + KeyAgreementController provideKeyAgreementController( + KeyAgreementControllerImpl keyAgreementController) { + return keyAgreementController; + } + @ActivityScope @Provides PasswordController providePasswordController( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java index 0bff14ed7c0e6637b1bf14d68a85e23a21a3d016..cce6bf20731f91deaf06e22240abe2bf9e82ca19 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java @@ -2,7 +2,7 @@ package org.briarproject.briar.android.keyagreement; import java.io.IOException; -class CameraException extends IOException { +public class CameraException extends IOException { CameraException(String message) { super(message); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/MailboxExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/MailboxExchangeActivity.java deleted file mode 100644 index 638c11017de04583f5c29c4453517afc62741c40..0000000000000000000000000000000000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/MailboxExchangeActivity.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.briarproject.briar.android.keyagreement; - -import android.os.Bundle; -import android.support.annotation.UiThread; -import android.widget.Toast; - -import org.briarproject.bramble.api.contact.ContactExchangeListener; -import org.briarproject.bramble.api.contact.ContactExchangeTask; -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.bramble.api.identity.IdentityManager; -import org.briarproject.bramble.api.identity.LocalAuthor; -import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; -import org.briarproject.briar.R; -import org.briarproject.briar.android.activity.ActivityComponent; - -import java.util.logging.Logger; - -import javax.annotation.Nullable; -import javax.inject.Inject; - -import static android.widget.Toast.LENGTH_LONG; -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER; -import static org.briarproject.bramble.api.contact.ContactType.PRIVATE_MAILBOX; -import static org.briarproject.bramble.util.LogUtils.logException; - -public class MailboxExchangeActivity extends KeyAgreementActivity implements - ContactExchangeListener { - - private static final Logger LOG = - Logger.getLogger(MailboxExchangeActivity.class.getName()); - - // Fields that are accessed from background threads must be volatile - @Inject - volatile ContactExchangeTask contactExchangeTask; - @Inject - volatile IdentityManager identityManager; - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - - @Override - public void onCreate(@Nullable Bundle state) { - super.onCreate(state); - getSupportActionBar().setTitle(R.string.mailbox_add); - } - - protected void startContactExchange(KeyAgreementResult result) { - runOnDbThread(() -> { - LocalAuthor localAuthor; - // Load the local pseudonym - try { - localAuthor = identityManager.getLocalAuthor(); - } catch (DbException e) { - logException(LOG, WARNING, e); - contactExchangeFailed(); - return; - } - - // Exchange contact details - contactExchangeTask.startExchange(MailboxExchangeActivity.this, - localAuthor, result.getMasterKey(), - result.getConnection(), result.getTransportId(), - result.wasAlice(), MAILBOX_OWNER, PRIVATE_MAILBOX); - }); - } - - @Override - public void contactExchangeSucceeded(Author remoteAuthor) { - runOnUiThreadUnlessDestroyed(() -> { - String contactName = remoteAuthor.getName(); - String format = getString(R.string.contact_added_toast); - String text = String.format(format, contactName); - Toast.makeText(MailboxExchangeActivity.this, text, LENGTH_LONG) - .show(); - supportFinishAfterTransition(); - }); - } - - @Override - public void duplicateContact(Author remoteAuthor) { - runOnUiThreadUnlessDestroyed(() -> { - String contactName = remoteAuthor.getName(); - String format = getString(R.string.contact_already_exists); - String text = String.format(format, contactName); - Toast.makeText(MailboxExchangeActivity.this, text, LENGTH_LONG) - .show(); - finish(); - }); - } - - @Override - public void contactExchangeFailed() { - runOnUiThreadUnlessDestroyed(() -> { - showErrorFragment(R.string.connection_error_explanation); - }); - } - - @UiThread - @Override - public void keyAgreementFailed() { - showErrorFragment(R.string.connection_error_explanation); - } - - @UiThread - @Override - public String keyAgreementWaiting() { - return getString(R.string.waiting_for_contact_to_scan); - } - - @UiThread - @Override - public String keyAgreementStarted() { - return getString(R.string.authenticating_with_device); - } - - @UiThread - @Override - public void keyAgreementAborted(boolean remoteAborted) { - showErrorFragment(R.string.connection_error_explanation); - } - - @UiThread - @Override - public String keyAgreementFinished(KeyAgreementResult result) { - startContactExchange(result); - return getString(R.string.exchanging_contact_details); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java index 7b018be03a96dab6abbb9836310071c5858685ff..efd8f62a80c8f02743fcc21158f8b055edce95e5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java @@ -29,7 +29,7 @@ import static java.util.logging.Level.WARNING; @SuppressWarnings("deprecation") @MethodsNotNullByDefault @ParametersNotNullByDefault -class QrCodeDecoder implements PreviewConsumer, PreviewCallback { +public class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private static final Logger LOG = Logger.getLogger(QrCodeDecoder.class.getName()); @@ -40,7 +40,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private Camera camera = null; private int cameraIndex = 0; - QrCodeDecoder(ResultCallback callback) { + public QrCodeDecoder(ResultCallback callback) { this.callback = callback; } @@ -141,6 +141,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { } @NotNullByDefault + public interface ResultCallback { void handleResult(Result result); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java index 617f84b996ef44fd7edbd68be76594970b261ce0..d330436da65ff312cef80e81f5878387ca6129b9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeUtils.java @@ -21,13 +21,14 @@ import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault +public class QrCodeUtils { private static final Logger LOG = Logger.getLogger(QrCodeUtils.class.getName()); @Nullable - static Bitmap createQrCode(DisplayMetrics dm, String input) { + public static Bitmap createQrCode(DisplayMetrics dm, String input) { int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels); try { // Generate QR code diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ExchangeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ExchangeFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..3196503954767b669ca4a8e9ecb91ac677362c2f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ExchangeFragment.java @@ -0,0 +1,105 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent; +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.fragment.BaseEventFragment; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ExchangeFragment extends BaseEventFragment { + static final String TAG = ExchangeFragment.class.getName(); + + private static final Logger LOG = Logger.getLogger(TAG); + @Inject + KeyAgreementController keyAgreementController; + + private TextView statusView; + private KeyAgreementEventListener listener; + + public static ExchangeFragment newInstance() { + Bundle args = new Bundle(); + ExchangeFragment fragment = new ExchangeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + listener = (KeyAgreementEventListener) context; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater + .inflate(R.layout.fragment_keyagreement_mailbox_exchange, + container, + false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + statusView = view.findViewById(R.id.nextButton); + if (savedInstanceState == null) + keyAgreementController.startKeyAgreement(); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); + } + + @Override + protected void finish() { + getActivity().getSupportFragmentManager().popBackStack(); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof KeyAgreementStartedEvent) { + runOnUiThreadUnlessDestroyed(() -> { + statusView.setText(R.string.authenticating_with_device); + }); + } else if (e instanceof KeyAgreementFinishedEvent) { + runOnUiThreadUnlessDestroyed(() -> { + keyAgreementController.startContactExchange( + ((KeyAgreementFinishedEvent) e).getResult(), listener); + statusView.setText(R.string.exchanging_contact_details); + }); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementController.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementController.java new file mode 100644 index 0000000000000000000000000000000000000000..a66ed61ebda9ffe6e3af5fab15712f88bcf72b96 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementController.java @@ -0,0 +1,38 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.contact.ContactExchangeListener; +import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; +import org.briarproject.bramble.api.keyagreement.Payload; +import org.briarproject.briar.android.controller.handler.UiResultHandler; + +public interface KeyAgreementController { + /** + * Call this when your lifecycle starts, + * so the listener will be called when information changes. + * + * @param resuming + */ + @UiThread + void onStart(boolean resuming); + + /** + * Call this when your lifecycle stops, + * so that the controller knows it can stops listening to events. + */ + @UiThread + void onStop(); + + void getQrCode(UiResultHandler<Payload> resultHandler); + + void setRemoteQrCode(Payload remotePayload); + + void startKeyAgreement(); + + void startContactExchange(KeyAgreementResult result, + ContactExchangeListener listener); + + void reset(); + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d6c2acc95df05c26422d36330c148c5009bcf36f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementControllerImpl.java @@ -0,0 +1,156 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.contact.ContactExchangeListener; +import org.briarproject.bramble.api.contact.ContactExchangeTask; +import org.briarproject.bramble.api.db.DatabaseExecutor; +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.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +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.event.KeyAgreementListeningEvent; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.briar.android.controller.handler.UiResultHandler; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; +import javax.inject.Provider; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER; +import static org.briarproject.bramble.api.contact.ContactType.PRIVATE_MAILBOX; +import static org.briarproject.bramble.util.LogUtils.logException; + +public class KeyAgreementControllerImpl implements KeyAgreementController, + EventListener { + + private static final Logger LOG = + Logger.getLogger(KeyAgreementControllerImpl.class.getName()); + + private final Executor dbExecutor, ioExecutor; + private final EventBus eventBus; + private final Provider<KeyAgreementTask> keyAgreementTaskProvider; + private volatile KeyAgreementTask task; + private volatile Payload localPayload, remotePayload; + private volatile CountDownLatch waitingForTask; + private IdentityManager identityManager; + private final ContactExchangeTask contactExchangeTask; + + @Inject + KeyAgreementControllerImpl(@DatabaseExecutor Executor dbExecutor, + @IoExecutor Executor ioExecutor, + EventBus eventBus, + Provider<KeyAgreementTask> keyAgreementTaskProvider, + IdentityManager identityManager, + ContactExchangeTask contactExchangeTask) { + this.dbExecutor = dbExecutor; + this.ioExecutor = ioExecutor; + this.eventBus = eventBus; + this.keyAgreementTaskProvider = keyAgreementTaskProvider; + this.identityManager = identityManager; + this.contactExchangeTask = contactExchangeTask; + } + + @Override + public void onStart(boolean resuming) { + eventBus.addListener(this); + if (!resuming) startListening(); + } + + @Override + public void onStop() { + eventBus.removeListener(this); + stopListening(); + } + + @Override + public void reset() { + localPayload = null; + startListening(); + } + + @UiThread + private void startListening() { + waitingForTask = new CountDownLatch(1); + 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 synchronized void getQrCode(UiResultHandler<Payload> resultHandler) { + ioExecutor.execute(() -> { + try { + waitingForTask.await(); + } catch (InterruptedException e) { + //TODO: Log + return; + } + resultHandler.onResultUi(localPayload); + }); + } + + @Override + public void setRemoteQrCode(Payload remotePayload) { + this.remotePayload = remotePayload; + } + + @Override + public void startKeyAgreement() { + if (remotePayload == null) throw new IllegalStateException(); + task.connectAndRunProtocol(remotePayload); + } + + @Override + public void startContactExchange(KeyAgreementResult result, + ContactExchangeListener listener) { + dbExecutor.execute(() -> { + LocalAuthor localAuthor; + // Load the local pseudonym + try { + localAuthor = identityManager.getLocalAuthor(); + } catch (DbException e) { + logException(LOG, WARNING, e); + listener.contactExchangeFailed(); + return; + } + + // Exchange contact details + contactExchangeTask.startExchange(listener, + localAuthor, result.getMasterKey(), + result.getConnection(), result.getTransportId(), + result.wasAlice(), MAILBOX_OWNER, PRIVATE_MAILBOX); + }); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof KeyAgreementListeningEvent) { + localPayload = + ((KeyAgreementListeningEvent) e).getLocalPayload(); + waitingForTask.countDown(); + } + + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementEventListener.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..c9bde3a14a28754c2fd0339d600729b52e900094 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/KeyAgreementEventListener.java @@ -0,0 +1,12 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import org.briarproject.bramble.api.contact.ContactExchangeListener; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface KeyAgreementEventListener extends ContactExchangeListener { + + void showCameraFragment(); + + void showExchangeFragment(); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/MailboxIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/MailboxIntroFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..e47d9623e774e3f9b37f00865193b4e0550f6ebe --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/MailboxIntroFragment.java @@ -0,0 +1,77 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +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.fragment.BaseFragment; + +import javax.annotation.Nullable; + +import static android.view.View.FOCUS_DOWN; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class MailboxIntroFragment extends BaseFragment { + + interface IntroScreenSeenListener { + void showNextScreen(); + } + + public static final String TAG = MailboxIntroFragment.class.getName(); + + private IntroScreenSeenListener screenSeenListener; + private ScrollView scrollView; + + public static MailboxIntroFragment newInstance() { + + Bundle args = new Bundle(); + + MailboxIntroFragment fragment = new MailboxIntroFragment(); + fragment.setArguments(args); + return fragment; + } + + public void injectFragment(ActivityComponent component) { +// component.inject(this); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + screenSeenListener = (IntroScreenSeenListener) context; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_keyagreement_id, container, + false); + scrollView = v.findViewById(R.id.scrollView); + View button = v.findViewById(R.id.continueButton); + button.setOnClickListener(view -> screenSeenListener.showNextScreen()); + return v; + } + + @Override + public void onStart() { + super.onStart(); + scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/MailboxKeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/MailboxKeyAgreementActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..8a4f013d787bedfed0aa4eb5a178383613c51d6e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/MailboxKeyAgreementActivity.java @@ -0,0 +1,366 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.StringRes; +import android.support.annotation.UiThread; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog.Builder; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.widget.Toast; + +import org.briarproject.bramble.api.contact.ContactExchangeListener; +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.identity.Author; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; +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.keyagreement.ContactExchangeErrorFragment; +import org.briarproject.briar.android.util.UiUtils; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.Manifest.permission.CAMERA; +import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE; +import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION.SDK_INT; +import static android.widget.Toast.LENGTH_LONG; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA; +import static org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; +import static org.briarproject.briar.android.keyagreement.mailbox.MailboxIntroFragment.IntroScreenSeenListener; +import static org.briarproject.briar.android.keyagreement.mailbox.MailboxIntroFragment.newInstance; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class MailboxKeyAgreementActivity extends BriarActivity implements + BaseFragmentListener, IntroScreenSeenListener, EventListener, + KeyAgreementEventListener, ContactExchangeListener { + + private static final Logger LOG = + Logger.getLogger(MailboxKeyAgreementActivity.class.getName()); + @Inject + EventBus eventBus; + @Inject + volatile KeyAgreementController keyAgreementController; + + private boolean isResumed = false, enableWasRequested = false; + private boolean continueClicked, gotCameraPermission; + private BluetoothState bluetoothState = BluetoothState.UNKNOWN; + private BroadcastReceiver bluetoothReceiver = null; + + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + keyAgreementController.onStart(true); + setContentView(R.layout.activity_fragment_container_toolbar); + // Disable screen timeout + findViewById(R.id.fragmentContainer).setKeepScreenOn(true); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + if (state == null) { + showInitialFragment(newInstance()); + } + IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED); + bluetoothReceiver = new BluetoothStateReceiver(); + registerReceiver(bluetoothReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onPostResume() { + super.onPostResume(); + isResumed = true; + // Workaround for + // https://code.google.com/p/android/issues/detail?id=190966 + if (canShowQrCodeFragment()) showCameraFragment(); + } + + private boolean canShowQrCodeFragment() { + return isResumed && continueClicked + && (SDK_INT < 23 || gotCameraPermission) + && bluetoothState != BluetoothState.UNKNOWN + && bluetoothState != BluetoothState.WAITING; + } + + @Override + public void onStart() { + super.onStart(); + eventBus.addListener(this); + } + + @Override + protected void onStop() { + super.onStop(); + eventBus.removeListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + isResumed = false; + } + + @Override + public void showNextScreen() { + continueClicked = true; + if (checkPermissions()) { + if (shouldRequestEnableBluetooth()) requestEnableBluetooth(); + else if (canShowQrCodeFragment()) showQrCodeFragment(); + } + } + + private boolean shouldRequestEnableBluetooth() { + return bluetoothState == BluetoothState.UNKNOWN + || bluetoothState == BluetoothState.REFUSED; + } + + private void requestEnableBluetooth() { + BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); + if (bt == null) { + setBluetoothState(BluetoothState.NO_ADAPTER); + } else if (bt.isEnabled()) { + setBluetoothState(BluetoothState.ENABLED); + } else { + enableWasRequested = true; + setBluetoothState(BluetoothState.WAITING); + Intent i = new Intent(ACTION_REQUEST_ENABLE); + startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH); + } + } + + private void setBluetoothState(BluetoothState bluetoothState) { + LOG.info("Setting Bluetooth state to " + bluetoothState); + this.bluetoothState = bluetoothState; + if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) { + eventBus.broadcast(new BluetoothEnabledEvent()); + enableWasRequested = false; + } + if (canShowQrCodeFragment()) showCameraFragment(); + } + + @Override + public void onActivityResult(int request, int result, Intent data) { + // If the request was granted we'll catch the state change event + if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED) + setBluetoothState(BluetoothState.REFUSED); + } + + public void showCameraFragment() { + // FIXME #824 + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentByTag(ScanQrCodeFragment.TAG) == null) { + BaseFragment f = ScanQrCodeFragment.newInstance(); + fm.beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); + } + } + + public void showExchangeFragment() { + // FIXME #824 + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentByTag(ExchangeFragment.TAG) == null) { + BaseFragment f = ExchangeFragment.newInstance(); + fm.beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); + } + } + + public void showQrCodeFragment() { + continueClicked = false; + // FIXME #824 + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentByTag(ShowQrCodeFragment.TAG) == null) { + BaseFragment f = ShowQrCodeFragment.newInstance(); + fm.beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); + } + } + + protected void showErrorFragment(@StringRes int errorResId) { + String errorMsg = getString(errorResId); + BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg); + showNextFragment(f); + } + + private boolean checkPermissions() { + if (ContextCompat.checkSelfPermission(this, CAMERA) != + PERMISSION_GRANTED) { + // Should we show an explanation? + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + CAMERA)) { + OnClickListener continueListener = + (dialog, which) -> requestPermission(); + Builder builder = new Builder(this, R.style.BriarDialogTheme); + builder.setTitle(R.string.permission_camera_title); + builder.setMessage(R.string.permission_camera_request_body); + builder.setNeutralButton(R.string.continue_button, + continueListener); + builder.show(); + } else { + requestPermission(); + } + gotCameraPermission = false; + return false; + } else { + gotCameraPermission = true; + return true; + } + } + + private void requestPermission() { + ActivityCompat.requestPermissions(this, new String[] {CAMERA}, + REQUEST_PERMISSION_CAMERA); + } + + @Override + @UiThread + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + if (requestCode == REQUEST_PERMISSION_CAMERA) { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && + grantResults[0] == PERMISSION_GRANTED) { + gotCameraPermission = true; + showNextScreen(); + } else { + if (!ActivityCompat.shouldShowRequestPermissionRationale(this, + CAMERA)) { + // The user has permanently denied the request + OnClickListener cancelListener = + (dialog, which) -> supportFinishAfterTransition(); + Builder builder = + new Builder(this, R.style.BriarDialogTheme); + builder.setTitle(R.string.permission_camera_title); + builder.setMessage(R.string.permission_camera_denied_body); + builder.setPositiveButton(R.string.ok, + UiUtils.getGoToSettingsListener(this)); + builder.setNegativeButton(R.string.cancel, cancelListener); + builder.show(); + } else { + Toast.makeText(this, + R.string.permission_camera_denied_toast, + LENGTH_LONG).show(); + supportFinishAfterTransition(); + } + } + } + } + + @Override + public void contactExchangeSucceeded(Author remoteAuthor) { + runOnUiThreadUnlessDestroyed(() -> { + String contactName = remoteAuthor.getName(); + String format = getString(R.string.contact_added_toast); + String text = String.format(format, contactName); + Toast.makeText(MailboxKeyAgreementActivity.this, text, LENGTH_LONG) + .show(); + supportFinishAfterTransition(); + }); + } + + @Override + public void duplicateContact(Author remoteAuthor) { + runOnUiThreadUnlessDestroyed(() -> { + String contactName = remoteAuthor.getName(); + String format = getString(R.string.contact_already_exists); + String text = String.format(format, contactName); + Toast.makeText(MailboxKeyAgreementActivity.this, text, LENGTH_LONG) + .show(); + finish(); + }); + } + + @Override + public void finish() { + keyAgreementController.onStop(); + } + + @Override + public void contactExchangeFailed() { + showErrorExplanationFragmentAndReset(); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof KeyAgreementFailedEvent) { + showErrorExplanationFragmentAndReset(); + } else if (e instanceof KeyAgreementAbortedEvent) { + showErrorExplanationFragmentAndReset(); + } + } + + private void showErrorExplanationFragmentAndReset() { + runOnUiThreadUnlessDestroyed( + () -> { + keyAgreementController.reset(); + showErrorFragment(R.string.connection_error_explanation); + }); + } + + private enum BluetoothState { + UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED + } + + private class BluetoothStateReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra(EXTRA_STATE, 0); + if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED); + else setBluetoothState(BluetoothState.UNKNOWN); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ScanQrCodeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ScanQrCodeFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..cf950d1674098bab54beee3c2429044b31345af5 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ScanQrCodeFragment.java @@ -0,0 +1,163 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.UiThread; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +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.keyagreement.PayloadParser; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.fragment.BaseFragment; +import org.briarproject.briar.android.keyagreement.CameraException; +import org.briarproject.briar.android.keyagreement.CameraView; +import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment; +import org.briarproject.briar.android.keyagreement.QrCodeDecoder; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.widget.Toast.LENGTH_LONG; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +public class ScanQrCodeFragment extends BaseFragment + implements QrCodeDecoder.ResultCallback { + + static final String TAG = ScanQrCodeFragment.class.getName(); + + private static final Logger LOG = Logger.getLogger(TAG); + private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + @Inject + PayloadParser payloadParser; + @Inject + KeyAgreementController keyAgreementController; + + private CameraView cameraView; + private KeyAgreementEventListener listener; + private volatile boolean gotRemotePayload; + + public static ScanQrCodeFragment newInstance() { + Bundle args = new Bundle(); + ScanQrCodeFragment fragment = new ScanQrCodeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + listener = (KeyAgreementEventListener) context; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_keyagreement_scan, container, + false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + cameraView = view.findViewById(R.id.camera_view); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); + cameraView.setPreviewConsumer(new QrCodeDecoder(this)); + } + + @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); + } + } + + @UiThread + private void logCameraExceptionAndFinish(CameraException e) { + logException(LOG, WARNING, e); + Toast.makeText(getActivity(), R.string.camera_error, + LENGTH_LONG).show(); + finish(); + } + + @Override + public void handleResult(Result result) { + runOnUiThreadUnlessDestroyed(() -> { + LOG.info("Got result from decoder"); + if (!gotRemotePayload) qrCodeScanned(result.getText()); + }); + } + + @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(); + keyAgreementController.setRemoteQrCode(remotePayload); + listener.showExchangeFragment(); + } catch (UnsupportedVersionException e) { + String msg = getString(R.string.qr_code_unsupported, + 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); + Toast.makeText(getActivity(), R.string.qr_code_invalid, + LENGTH_LONG).show(); + } + } + + @Override + protected void finish() { + getActivity().getSupportFragmentManager().popBackStack(); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ShowQrCodeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ShowQrCodeFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..11de9c6019bc400cca108de16b9f573154130a02 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/mailbox/ShowQrCodeFragment.java @@ -0,0 +1,137 @@ +package org.briarproject.briar.android.keyagreement.mailbox; + +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.keyagreement.Payload; +import org.briarproject.bramble.api.keyagreement.PayloadEncoder; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +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.controller.handler.UiResultHandler; +import org.briarproject.briar.android.fragment.BaseEventFragment; +import org.briarproject.briar.android.keyagreement.QrCodeUtils; +import org.briarproject.briar.android.view.QrCodeView; + +import java.nio.charset.Charset; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static java.util.logging.Level.INFO; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ShowQrCodeFragment extends BaseEventFragment { + static final String TAG = ShowQrCodeFragment.class.getName(); + + private static final Logger LOG = Logger.getLogger(TAG); + private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + @Inject + PayloadEncoder payloadEncoder; + @Inject + @IoExecutor + Executor ioExecutor; + @Inject + KeyAgreementController keyAgreementController; + + private QrCodeView qrCodeView; + private Button nextButton; + + private KeyAgreementEventListener listener; + + public static ShowQrCodeFragment newInstance() { + Bundle args = new Bundle(); + ShowQrCodeFragment fragment = new ShowQrCodeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + listener = (KeyAgreementEventListener) context; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater + .inflate(R.layout.fragment_keyagreement_mailbox_qr, container, + false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + qrCodeView = view.findViewById(R.id.qr_code_view); + nextButton = view.findViewById(R.id.nextButton); + nextButton.setOnClickListener((it) -> listener.showCameraFragment()); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); + keyAgreementController.getQrCode(new UiResultHandler<Payload>(this) { + @Override + public void onResultUi(@Nonnull Payload payload) { + showQrCode(payload); + } + }); + } + + private void showQrCode(Payload payload) { + Context context = getContext(); + if (context == null) return; + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + ioExecutor.execute(() -> { + byte[] payloadBytes = payloadEncoder.encode(payload); + 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 + protected void finish() { + getActivity().getSupportFragmentManager().popBackStack(); + } + + @Override + public void eventOccurred(Event e) { + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxFragment.java index adccee62803f5d9aad8d5a2161ad368877aec3db..74f93629497c72d67a454e274f5cf42262d4272a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/mailbox/MailboxFragment.java @@ -27,7 +27,7 @@ import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.fragment.BaseFragment; -import org.briarproject.briar.android.keyagreement.MailboxExchangeActivity; +import org.briarproject.briar.android.keyagreement.mailbox.MailboxKeyAgreementActivity; import java.util.logging.Logger; @@ -99,7 +99,8 @@ public class MailboxFragment extends BaseFragment implements EventListener { switch (item.getItemId()) { case R.id.action_add_contact: Intent intent = - new Intent(getContext(), MailboxExchangeActivity.class); + new Intent(getContext(), + MailboxKeyAgreementActivity.class); startActivity(intent); return true; default: diff --git a/briar-android/src/main/res/layout/fragment_keyagreement_mailbox_exchange.xml b/briar-android/src/main/res/layout/fragment_keyagreement_mailbox_exchange.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac8d084f955337d7098eef9a0b6d7750ddcd4cb9 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_keyagreement_mailbox_exchange.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/status" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:padding="@dimen/margin_large" + android:text="Exchanging contact details" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + +</android.support.constraint.ConstraintLayout> diff --git a/briar-android/src/main/res/layout/fragment_keyagreement_mailbox_qr.xml b/briar-android/src/main/res/layout/fragment_keyagreement_mailbox_qr.xml new file mode 100644 index 0000000000000000000000000000000000000000..a29c7be4d5c787d5e3fec434ff81a6ecbf25749c --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_keyagreement_mailbox_qr.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <org.briarproject.briar.android.view.QrCodeView + android:id="@+id/qr_code_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:background="@android:color/white" + app:layout_constraintBottom_toTopOf="@+id/instruction" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <TextView + android:id="@+id/instruction" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/window_background" + android:gravity="center" + android:paddingBottom="@dimen/margin_large" + android:paddingTop="@dimen/margin_large" + android:text="Scan this QrCode with the phone the Briar Mailbox is running on. Once done press Next." + app:layout_constraintBottom_toTopOf="@+id/next" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/qr_code_view"/> + + <Button + android:id="@+id/nextButton" + style="@style/BriarButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/setup_next" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/instruction"/> + +</android.support.constraint.ConstraintLayout> diff --git a/briar-android/src/main/res/layout/fragment_keyagreement_scan.xml b/briar-android/src/main/res/layout/fragment_keyagreement_scan.xml new file mode 100644 index 0000000000000000000000000000000000000000..f3d0c2db146a1424a869040b5168a6ee06a56162 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_keyagreement_scan.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + android:id="@+id/frameLayout" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <org.briarproject.briar.android.keyagreement.CameraView + android:id="@+id/camera_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <TextView + android:id="@+id/instruction" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/window_background" + android:gravity="center" + android:paddingBottom="@dimen/margin_large" + android:paddingTop="@dimen/margin_large" + android:text="Scan the QrCode on your Briar phone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:text="Scan the QrCode on your Briar phone"/> + + <TextView + android:id="@+id/status" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:padding="@dimen/margin_large" + android:text="Exchanging contact details" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + +</android.support.constraint.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml b/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml index 449f5b905d5ab535aa492c997e3a85da684824bd..ac6d90d825bdb06538689e78e804ea6ef3ddc87b 100644 --- a/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml +++ b/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml @@ -40,9 +40,9 @@ android:max="100" android:progress="100" android:rotation="180" - app:layout_constraintTop_toTopOf="@+id/instruction" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent"/> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/instruction"/> <TextView android:id="@+id/status" @@ -52,7 +52,7 @@ android:padding="@dimen/margin_large" android:text="Exchanging contact details" android:visibility="invisible" - app:layout_constraintBottom_toTopOf="@+id/timer" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>