From c09dc7785999ea1f70e943ec47b59b47f2fd17d0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 24 Oct 2020 19:25:28 +0200 Subject: [PATCH] Progress on generating IKeys --- app/build.gradle | 9 + .../messenger/RequeryDatabaseTest.java | 167 +++-------- .../messenger/di/AndroidTestComponent.java | 21 ++ .../messenger/di/TestDatabaseModule.java | 37 +++ app/src/main/AndroidManifest.xml | 4 +- .../android/di/component/AppComponent.java | 3 +- .../detail/AccountDetailsActivity.java | 9 +- .../detail/AccountDetailsFragment.java | 143 ++++++++-- .../AndroidAccountDetailsViewModel.java | 39 ++- .../detail/ContactDetailActivity.java | 2 +- .../IkeyBackupCreationAndroidViewModel.java | 80 ++++++ .../ui/ikey/IkeyBackupCreationFragment.java | 62 +++++ .../ui/ikey/IkeyBackupRestoreActivity.java | 16 ++ .../ToggleableFingerprintsAdapter.java | 2 +- .../android/util/QrCodeGenerator.java | 20 ++ .../layout/activity_fragment_container.xml | 20 +- .../layout/activity_ikey_backup_restore.xml | 80 ++++++ app/src/main/res/layout/activity_main.xml | 21 +- .../res/layout/fragment_account_details.xml | 260 +++++++++++------- .../layout/fragment_ikey_backup_creation.xml | 49 ++++ .../main/res/layout/layout_top_toolbar.xml | 34 +++ app/src/main/res/layout/switch_item.xml | 11 + .../view_fingerprints_card_toggleable.xml | 2 +- ...int.xml => view_openpgp_4_fingerprint.xml} | 2 +- ...ml => view_openpgp_4_fingerprint_card.xml} | 2 +- ...view_openpgp_4_fingerprint_toggleable.xml} | 2 +- .../main/res/layout/view_toolbar_bottom.xml | 9 + app/src/main/res/layout/view_toolbar_top.xml | 8 + .../main/res/menu/menu_account_details.xml | 26 ++ app/src/main/res/menu/menu_main.xml | 1 + app/src/main/res/values/strings.xml | 2 +- .../mercury_im/messenger/ExampleUnitTest.java | 23 -- data/build.gradle | 3 + .../data/model/AbstractIkeyRecordModel.java | 1 - .../model/AbstractIkeySecretKeyModel.java | 2 + .../model/AbstractIkeySubordinateModel.java | 1 - .../data/repository/RxIkeyRepository.java | 28 +- .../component/InMemoryDatabaseComponent.java | 4 +- ...ule.java => SqliteTestDatabaseModule.java} | 2 +- domain/build.gradle | 6 +- .../jivesoftware/smackx/ikey/IkeyManager.java | 89 +++++- .../ikey/record/FileBasedIkeyStore.java | 22 ++ .../smackx/ikey/record/IkeyStore.java | 10 + .../smackx/ikey_ox/OxIkeyManager.java | 95 ------- .../smackx/ikey_ox/OxIkeyStore.java | 15 - .../core/crypto/ikey/IkeyInitializer.java | 32 +++ .../core/crypto/ikey/IkeyStoreAdapter.java | 19 +- .../core/crypto/ikey/MercuryIkeyManager.java | 54 ---- .../data/repository/IkeyKeyRepository.java | 12 +- .../data/repository/IkeyRecordRepository.java | 5 +- .../core/di/module/ViewModelModule.java | 8 +- .../messenger/core/util/DefaultUtil.java | 16 ++ .../detail/AccountDetailsViewModel.java | 44 ++- .../ikey/IkeyInitializationViewModel.java | 61 ++++ .../IkeySecretKeyBackupCreationViewModel.java | 26 +- .../IkeySecretKeyBackupRestoreViewModel.java | 7 + .../smackx/ikey/element/IkeyElementTest.java | 2 + .../IkeySignatureCreatorAndVerifierTest.java | 1 + .../smackx/ikey/element/XepTest.java | 11 +- .../mercury_im/messenger/entity/Account.java | 2 +- libs/Smack | 2 +- version.gradle | 2 +- 62 files changed, 1216 insertions(+), 532 deletions(-) create mode 100644 app/src/androidTest/java/org/mercury_im/messenger/di/AndroidTestComponent.java create mode 100644 app/src/androidTest/java/org/mercury_im/messenger/di/TestDatabaseModule.java create mode 100644 app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java create mode 100644 app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java create mode 100644 app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java create mode 100644 app/src/main/java/org/mercury_im/messenger/android/util/QrCodeGenerator.java create mode 100644 app/src/main/res/layout/activity_ikey_backup_restore.xml create mode 100644 app/src/main/res/layout/fragment_ikey_backup_creation.xml create mode 100644 app/src/main/res/layout/layout_top_toolbar.xml create mode 100644 app/src/main/res/layout/switch_item.xml rename app/src/main/res/layout/{view_fingerprint.xml => view_openpgp_4_fingerprint.xml} (79%) rename app/src/main/res/layout/{view_fingerprint_card.xml => view_openpgp_4_fingerprint_card.xml} (94%) rename app/src/main/res/layout/{view_fingerprint_toggleable.xml => view_openpgp_4_fingerprint_toggleable.xml} (94%) create mode 100644 app/src/main/res/layout/view_toolbar_bottom.xml create mode 100644 app/src/main/res/layout/view_toolbar_top.xml create mode 100644 app/src/main/res/menu/menu_account_details.xml delete mode 100644 app/src/test/java/org/mercury_im/messenger/ExampleUnitTest.java rename data/src/test/java/org/mercury_im/messenger/data/di/module/{TestDatabaseModule.java => SqliteTestDatabaseModule.java} (98%) delete mode 100644 domain/src/main/java/org/jivesoftware/smackx/ikey_ox/OxIkeyManager.java delete mode 100644 domain/src/main/java/org/jivesoftware/smackx/ikey_ox/OxIkeyStore.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/core/crypto/ikey/IkeyInitializer.java delete mode 100644 domain/src/main/java/org/mercury_im/messenger/core/crypto/ikey/MercuryIkeyManager.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/core/util/DefaultUtil.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/core/viewmodel/ikey/IkeyInitializationViewModel.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/core/viewmodel/ikey/IkeySecretKeyBackupRestoreViewModel.java diff --git a/app/build.gradle b/app/build.gradle index f6d65cd..31dd148 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -82,7 +82,10 @@ dependencies { // Dagger 2 for dependency injection implementation "com.google.dagger:dagger:$daggerVersion" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" + androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" compileOnly "org.projectlombok:lombok:$lombokVersion" annotationProcessor "org.projectlombok:lombok:$lombokVersion" @@ -91,6 +94,9 @@ dependencies { implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion" annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycleVersion" + + // Android extension for rxJava api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" @@ -110,6 +116,9 @@ dependencies { // circular image viewer for avatars implementation 'de.hdodenhof:circleimageview:3.0.1' + implementation 'com.google.zxing:core:3.2.1' + implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar' + // Android specific classes of Smacks API implementation "org.igniterealtime.smack:smack-android-extensions:$smackAndroidExtensionsVersion" diff --git a/app/src/androidTest/java/org/mercury_im/messenger/RequeryDatabaseTest.java b/app/src/androidTest/java/org/mercury_im/messenger/RequeryDatabaseTest.java index 44379e3..f01a997 100644 --- a/app/src/androidTest/java/org/mercury_im/messenger/RequeryDatabaseTest.java +++ b/app/src/androidTest/java/org/mercury_im/messenger/RequeryDatabaseTest.java @@ -1,149 +1,66 @@ package org.mercury_im.messenger; -import android.content.Context; - -import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.jxmpp.jid.impl.JidCreate; -import org.mercury_im.messenger.xmpp.requery.entity.AccountModel; -import org.mercury_im.messenger.xmpp.requery.entity.ChatModel; -import org.mercury_im.messenger.xmpp.requery.entity.ContactModel; -import org.mercury_im.messenger.xmpp.requery.entity.EntityModel; -import org.mercury_im.messenger.xmpp.requery.entity.LastReadChatMessageRelation; -import org.mercury_im.messenger.xmpp.requery.entity.MessageModel; -import org.mercury_im.messenger.xmpp.requery.entity.Models; -import org.mercury_im.messenger.core.requery.enums.SubscriptionDirection; +import org.mercury_im.messenger.core.data.repository.AccountRepository; +import org.mercury_im.messenger.di.DaggerAndroidTestComponent; +import org.mercury_im.messenger.entity.Account; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import io.requery.Persistable; -import io.requery.android.sqlite.DatabaseSource; -import io.requery.reactivex.ReactiveEntityStore; -import io.requery.reactivex.ReactiveSupport; -import io.requery.sql.Configuration; -import io.requery.sql.EntityDataStore; -import io.requery.sql.TableCreationMode; +import javax.inject.Inject; + +import io.reactivex.observers.TestObserver; @RunWith(AndroidJUnit4.class) public class RequeryDatabaseTest { - static ReactiveEntityStore dataStore; + @Inject + AccountRepository accountRepository; - @BeforeClass - public static void setup() { - Context appContext = InstrumentationRegistry.getTargetContext(); - DatabaseSource source = new DatabaseSource(appContext, Models.DEFAULT, - "mercury_test_db", 1); - // use this in development mode to drop and recreate the tables on every upgrade - source.setTableCreationMode(TableCreationMode.DROP_CREATE); - source.setLoggingEnabled(true); - Configuration configuration = source.getConfiguration(); - dataStore = ReactiveSupport.toReactiveStore( - new EntityDataStore<>(configuration)); + @Before + public void setup() { + DaggerAndroidTestComponent.builder().build().inject(this); } @Test - public void databaseTest() throws InterruptedException { - AccountModel accountModel = new AccountModel(); - accountModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("juliet@capulet.lit")); - accountModel.setPassword("romeo0romeo"); - accountModel.setRosterVersion(null); - accountModel.setEnabled(true); - - Disposable accounts = dataStore.select(AccountModel.class).get().observableResult().subscribe( - accountModels -> accountModels.forEach(System.out::println)); - - Disposable entities = dataStore.select(EntityModel.class).get().observableResult() - .subscribeOn(Schedulers.io()) - .subscribe(entityModels -> entityModels.forEach(System.out::println)); - - Disposable contacts = dataStore.select(ContactModel.class).get() - .observableResult().subscribeOn(Schedulers.io()).subscribe( - contactModels -> contactModels.forEach(System.out::println)); - - Thread.sleep(100); - - dataStore.upsert(accountModel).subscribeOn(Schedulers.io()) - .subscribe(); - - ContactModel contactModel = new ContactModel(); - EntityModel entityModel = new EntityModel(); - entityModel.setAccount(accountModel); - entityModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("romeo@capulet.lit")); - contactModel.setEntity(entityModel); - contactModel.setRostername("Romeo"); - contactModel.setSub_direction(SubscriptionDirection.both); - dataStore.insert(contactModel).blockingGet(); - - dataStore.select(AccountModel.ENABLED, ContactModel.ROSTERNAME) - .from(AccountModel.class) - .join(EntityModel.class).on(AccountModel.ID.eq(EntityModel.ACCOUNT_ID)) - .join(ContactModel.class).on(EntityModel.ID.eq(ContactModel.ENTITY_ID)) - .get().observableResult().blockingForEach(e -> e.forEach(System.out::println)); - - Thread.sleep(10000); - accounts.dispose(); - entities.dispose(); - contacts.dispose(); - } - - public static class ContactDetail { - private boolean enabled; - private String rostername; - } - - @Test - public void test2() { - AccountModel account = new AccountModel(); - account.setJid(JidCreate.entityBareFromOrThrowUnchecked("omemouser@jabberhead.tk")); + public void insertQueryDeleteAccountTest() { + Account account = new Account(); account.setEnabled(true); - account.setPassword("nöö"); + account.setPassword("sw0rdf1sh"); + account.setAddress("alice@wonderland.lit"); + account.setRosterVersion(""); + account.setHost("wonderland.lit"); + account.setPort(5222); - EntityModel entity = new EntityModel(); - entity.setJid(JidCreate.entityBareFromOrThrowUnchecked("jabbertest@test.test")); - entity.setAccount(account); + // Insert account into database + TestObserver testObserver = new TestObserver<>(); + accountRepository.upsertAccount(account) + .subscribe(testObserver); - ContactModel contact = new ContactModel(); - contact.setEntity(entity); - contact.setRostername("Olaf"); - contact.setSub_direction(SubscriptionDirection.both); - contact.setSub_approved(false); - contact.setSub_pending(true); + testObserver.assertComplete(); + testObserver.assertNoErrors(); + testObserver.assertNoTimeout(); + testObserver.assertValue(account); - dataStore.upsert(contact).blockingGet(); + // delete account from database + testObserver = new TestObserver<>(); + accountRepository.deleteAccount(account) + .subscribe(testObserver); - ChatModel chat = new ChatModel(); - chat.setPeer(entity); - chat.setDisplayed(true); + testObserver.assertComplete(); + testObserver.assertNoErrors(); + testObserver.assertNoTimeout(); - dataStore.upsert(chat).blockingGet(); + // assert no account in database + testObserver = new TestObserver<>(); + accountRepository.getAccount(account.getId()).subscribe(testObserver); - MessageModel message = new MessageModel(); - message.setBody("Hallo Welt!"); - message.setChat(chat); - - dataStore.upsert(message).blockingGet(); - - LastReadChatMessageRelation lastRead = new LastReadChatMessageRelation(); - lastRead.setChat(chat); - lastRead.setMessage(message); - - dataStore.upsert(lastRead).blockingGet(); - - MessageModel message2 = new MessageModel(); - message2.setChat(chat); - message2.setBody("How are you?"); - - dataStore.upsert(message2).blockingGet(); - - LastReadChatMessageRelation lastRead2 = new LastReadChatMessageRelation(); - lastRead2.setChat(chat); - lastRead2.setMessage(message2); - - dataStore.upsert(lastRead2).blockingGet(); + testObserver.assertComplete(); + testObserver.assertNoErrors(); + testObserver.assertNoValues(); } + + } diff --git a/app/src/androidTest/java/org/mercury_im/messenger/di/AndroidTestComponent.java b/app/src/androidTest/java/org/mercury_im/messenger/di/AndroidTestComponent.java new file mode 100644 index 0000000..e2de0b0 --- /dev/null +++ b/app/src/androidTest/java/org/mercury_im/messenger/di/AndroidTestComponent.java @@ -0,0 +1,21 @@ +package org.mercury_im.messenger.di; + +import org.mercury_im.messenger.RequeryDatabaseTest; +import org.mercury_im.messenger.data.di.MappingModule; +import org.mercury_im.messenger.data.di.RepositoryModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestDatabaseModule.class, + RepositoryModule.class, + MappingModule.class +}) +public interface AndroidTestComponent { + + void inject(RequeryDatabaseTest test); + +} diff --git a/app/src/androidTest/java/org/mercury_im/messenger/di/TestDatabaseModule.java b/app/src/androidTest/java/org/mercury_im/messenger/di/TestDatabaseModule.java new file mode 100644 index 0000000..948bc87 --- /dev/null +++ b/app/src/androidTest/java/org/mercury_im/messenger/di/TestDatabaseModule.java @@ -0,0 +1,37 @@ +package org.mercury_im.messenger.di; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; + +import org.mercury_im.messenger.data.model.Models; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import io.requery.Persistable; +import io.requery.android.sqlite.DatabaseSource; +import io.requery.reactivex.ReactiveEntityStore; +import io.requery.reactivex.ReactiveSupport; +import io.requery.sql.Configuration; +import io.requery.sql.EntityDataStore; +import io.requery.sql.TableCreationMode; + +@Module +public class TestDatabaseModule { + + @Provides + @Singleton + public static ReactiveEntityStore provideDataStore() { + Context appContext = InstrumentationRegistry.getTargetContext(); + DatabaseSource source = new DatabaseSource(appContext, Models.DEFAULT, + "mercury_test_db", 1); + // use this in development mode to drop and recreate the tables on every upgrade + source.setTableCreationMode(TableCreationMode.DROP_CREATE); + source.setLoggingEnabled(true); + Configuration configuration = source.getConfiguration(); + return ReactiveSupport.toReactiveStore(new EntityDataStore<>(configuration)); + } + +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d79768..24acbbd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ - @@ -18,9 +17,10 @@ android:roundIcon="@drawable/ic_mercury_icon" android:supportsRtl="true" android:theme="@style/Theme.Mercury"> + + android:label="Chat" /> diff --git a/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java b/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java index 014e7da..ea2fbc7 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java +++ b/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java @@ -5,6 +5,7 @@ import org.mercury_im.messenger.android.di.module.AndroidDatabaseModule; import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule; import org.mercury_im.messenger.android.ui.account.detail.AndroidAccountDetailsViewModel; import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel; +import org.mercury_im.messenger.android.ui.ikey.IkeyBackupCreationAndroidViewModel; import org.mercury_im.messenger.core.di.module.OpenPgpModule; import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule; import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule; @@ -93,7 +94,7 @@ public interface AppComponent { void inject(AndroidAccountDetailsViewModel accountDetailsViewModel); - //void inject(AndroidOxSecretKeyBackupRestoreViewModel viewModel); + void inject(IkeyBackupCreationAndroidViewModel ikeyBackupCreationAndroidViewModel); // Common VMs diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsActivity.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsActivity.java index e2a9dcd..a25992d 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import org.mercury_im.messenger.R; import org.mercury_im.messenger.android.ui.MercuryActivity; @@ -11,6 +12,7 @@ import org.mercury_im.messenger.android.util.ArgumentUtils; import java.util.UUID; +import butterknife.BindView; import butterknife.ButterKnife; import lombok.Value; @@ -20,6 +22,9 @@ public class AccountDetailsActivity extends AppCompatActivity implements Mercury private UUID accountId; + @BindView(R.id.toolbar) + Toolbar toolbar; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -32,12 +37,14 @@ public class AccountDetailsActivity extends AppCompatActivity implements Mercury } private void bindUiComponents() { - setContentView(R.layout.activity_fragment_container); + setContentView(R.layout.layout_top_toolbar); ButterKnife.bind(this); getSupportFragmentManager().beginTransaction() .replace(R.id.fragment, new AccountDetailsFragment(accountId), "account_details") .commit(); + + setSupportActionBar(toolbar); } private Arguments getArguments(Bundle bundle) { diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java index d464bc6..924b119 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AccountDetailsFragment.java @@ -4,6 +4,9 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -11,6 +14,8 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.RecyclerView; @@ -19,10 +24,14 @@ import com.google.android.material.card.MaterialCardView; import org.mercury_im.messenger.R; import org.mercury_im.messenger.android.MercuryImApplication; +import org.mercury_im.messenger.android.ui.ikey.IkeyBackupCreationFragment; import org.mercury_im.messenger.android.ui.openpgp.ToggleableFingerprintsAdapter; import org.mercury_im.messenger.android.ui.openpgp.OpenPgpV4FingerprintFormatter; +import org.mercury_im.messenger.core.util.Optional; +import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.pgpainless.key.OpenPgpV4Fingerprint; +import java.util.List; import java.util.UUID; import butterknife.BindView; @@ -40,6 +49,15 @@ public class AccountDetailsFragment extends Fragment { @BindView(R.id.btn_share) Button localFingerprintShareButton; + @BindView(R.id.btn_share_ikey) + Button ikeyFingerprintShareButton; + + @BindView(R.id.btn_backup_ikey) + Button ikeyCreateBackupButton; + + @BindView(R.id.ikey_fingerprint) + TextView ikeyFingerprint; + @BindView(R.id.local_fingerprint) TextView localFingerprint; @@ -49,8 +67,11 @@ public class AccountDetailsFragment extends Fragment { @BindView(R.id.other_fingerprints_card) MaterialCardView otherFingerprintsLayout; + @BindView(R.id.layout_ikey) + MaterialCardView ikeyLayout; + private final UUID accountId; - private ToggleableFingerprintsAdapter adapter; + private ToggleableFingerprintsAdapter otherFingerprintsAdapter; private AndroidAccountDetailsViewModel viewModel; @@ -58,19 +79,6 @@ public class AccountDetailsFragment extends Fragment { this.accountId = accountId; } - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_account_details, container, false); - ButterKnife.bind(this, view); - - externalFingerprintRecyclerView.setAdapter(adapter); - - observe(); - - return view; - } - @Override public void onAttach(@NonNull Context context) { super.onAttach(context); @@ -79,33 +87,110 @@ public class AccountDetailsFragment extends Fragment { viewModel = new ViewModelProvider(this, factory) .get(AndroidAccountDetailsViewModel.class); - this.adapter = new ToggleableFingerprintsAdapter( + this.otherFingerprintsAdapter = new ToggleableFingerprintsAdapter( (fingerprint, checked) -> viewModel.markFingerprintTrusted(fingerprint, checked)); - this.adapter.setItemLongClickListener(fingerprint -> viewModel.unpublishPublicKey(fingerprint)); + this.otherFingerprintsAdapter.setItemLongClickListener(fingerprint -> viewModel.unpublishPublicKey(fingerprint)); } - private void observe() { - viewModel.getLocalFingerprint().observe(getViewLifecycleOwner(), - f -> localFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(f))); + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + inflater.inflate(R.menu.menu_account_details, menu); + super.onCreateOptionsMenu(menu, inflater); + } - viewModel.getRemoteFingerprints().observe(getViewLifecycleOwner(), items -> { - otherFingerprintsLayout.setVisibility(items.isEmpty() ? View.GONE : View.VISIBLE); - adapter.setItems(items); - }); + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + + case R.id.action_generate_ikey: + viewModel.onGenerateIkey(); + return true; + + case R.id.action_delete_ikey: + viewModel.onDeleteIkey(); + return true; + + case R.id.action_restore_ikey_backup: + viewModel.onRestoreIkeyBackup(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_account_details, container, false); + ButterKnife.bind(this, view); + setHasOptionsMenu(true); + + externalFingerprintRecyclerView.setAdapter(otherFingerprintsAdapter); + observeViewModel(); + + return view; + } + + private void observeViewModel() { + viewModel.getIkeyFingerprint().observe(getViewLifecycleOwner(), this::displayIkeyFingerprint); + viewModel.getLocalFingerprint().observe(getViewLifecycleOwner(), this::displayLocalOxFingerprint); + viewModel.getRemoteFingerprints().observe(getViewLifecycleOwner(), this::displayOtherOxFingerprints); viewModel.getJid().observe(getViewLifecycleOwner(), accountJid -> jid.setText(accountJid.toString())); localFingerprintShareButton.setOnClickListener(v -> { OpenPgpV4Fingerprint fingerprint = viewModel.getLocalFingerprint().getValue(); - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "openpgp4fpr:" + fingerprint); - sendIntent.setType("text/plain"); + startShareFingerprintIntent(fingerprint); + }); - Intent shareIntent = Intent.createChooser(sendIntent, "Share OpenPGP Fingerprint"); - startActivity(shareIntent); + ikeyFingerprintShareButton.setOnClickListener(v -> { + Optional fingerprint = viewModel.getIkeyFingerprint().getValue(); + if (fingerprint == null || !fingerprint.isPresent()) { + return; + } + startShareFingerprintIntent(fingerprint.getItem()); + }); + ikeyCreateBackupButton.setOnClickListener(v -> { + getParentFragmentManager().beginTransaction() + .replace(R.id.fragment, IkeyBackupCreationFragment.newInstance(accountId)).commit(); }); } + private void startShareFingerprintIntent(OpenPgpV4Fingerprint fingerprint) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, "openpgp4fpr:" + fingerprint); + sendIntent.setType("text/plain"); + + Intent shareIntent = Intent.createChooser(sendIntent, "Share OpenPGP Fingerprint"); + startActivity(shareIntent); + } + + private void displayIkeyFingerprint(Optional fingerprint) { + if (fingerprint.isPresent()) { + ikeyLayout.setVisibility(View.VISIBLE); + ikeyFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(fingerprint.getItem())); + } else { + ikeyLayout.setVisibility(View.GONE); + ikeyFingerprint.setText(null); + } + } + + private void displayLocalOxFingerprint(OpenPgpV4Fingerprint fingerprint) { + if (fingerprint != null) { + localFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(fingerprint)); + } else { + localFingerprint.setText(null); + } + } + + private void displayOtherOxFingerprints(List fingerprints) { + otherFingerprintsAdapter.setItems(fingerprints); + if (fingerprints.isEmpty()) { + otherFingerprintsLayout.setVisibility(View.GONE); + } else { + otherFingerprintsLayout.setVisibility(View.VISIBLE); + } + } + } diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java index c26cb64..f39f3f9 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java @@ -14,6 +14,7 @@ import org.jxmpp.jid.impl.JidCreate; import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel; import org.mercury_im.messenger.core.SchedulersFacade; +import org.mercury_im.messenger.core.util.DefaultUtil; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; @@ -41,9 +42,10 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements AccountDetailsViewModel commonViewModel; private final UUID accountId; - private MutableLiveData localFingerprint = new MutableLiveData<>(new OpenPgpV4Fingerprint("09858F60046289311743B90F3152226EB43287C5")); + private MutableLiveData> ikeyFingerprint = new MutableLiveData<>(); + private MutableLiveData localFingerprint = new MutableLiveData<>(); private MutableLiveData> remoteFingerprints = new MutableLiveData<>(new ArrayList<>()); - private MutableLiveData jid = new MutableLiveData<>(JidCreate.entityBareFromOrThrowUnchecked("placeholder@place.holder")); + private MutableLiveData jid = new MutableLiveData<>(DefaultUtil.defaultJid()); public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) { super(application); @@ -58,6 +60,11 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements .map(Optional::getItem) .subscribe(localFingerprint::postValue)); + addDisposable(getCommonViewModel().observeIkeyFingerprint(accountId) + .compose(schedulers.executeUiSafeObservable()) + .subscribe(ikeyFingerprint::postValue, + e -> LOGGER.log(Level.SEVERE, "Error displaying ikey fingerprint", e))); + addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId) .compose(schedulers.executeUiSafeObservable()) .subscribe(list -> { @@ -83,6 +90,10 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements getCommonViewModel().markFingerprintTrusted(accountId, fingerprint, trusted); } + public LiveData> getIkeyFingerprint() { + return ikeyFingerprint; + } + public LiveData getLocalFingerprint() { return localFingerprint; } @@ -99,6 +110,30 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements e -> LOGGER.log(Level.SEVERE, "Error unpublishing fingerprint " + fingerprint, e))); } + public void onGenerateIkey() { + addDisposable(getCommonViewModel().generateIkey(accountId) + .subscribeOn(schedulers.getNewThread()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(() -> LOGGER.log(Level.INFO, "IKey generated for account " + accountId), + e -> LOGGER.log(Level.SEVERE, "Could not generate Ikey", e))); + } + + public void onDeleteIkey() { + addDisposable(getCommonViewModel().deleteIkey(accountId) + .subscribeOn(schedulers.getNewThread()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(() -> LOGGER.log(Level.INFO, "IKey deleted for account " + accountId), + e -> LOGGER.log(Level.SEVERE, "Could not delete Ikey", e))); + } + + public void onRestoreIkeyBackup() { + addDisposable(getCommonViewModel().restoreIkeyBackup(accountId) + .subscribeOn(schedulers.getNewThread()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(() -> LOGGER.log(Level.INFO, "IKey restored for account " + accountId), + e -> LOGGER.log(Level.SEVERE, "Could not restore Ikey backup", e))); + } + public static class AndroidAccountDetailsViewModelFactory implements ViewModelProvider.Factory { private final Application application; diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java index 415eaa6..7f518d8 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java @@ -39,7 +39,7 @@ public class ContactDetailActivity extends AppCompatActivity implements MercuryA } private void bindUiComponents() { - setContentView(R.layout.activity_fragment_container); + setContentView(R.layout.layout_top_toolbar); ButterKnife.bind(this); getSupportFragmentManager().beginTransaction() diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java new file mode 100644 index 0000000..0e40e92 --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationAndroidViewModel.java @@ -0,0 +1,80 @@ +package org.mercury_im.messenger.android.ui.ikey; + +import android.graphics.Bitmap; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; +import org.mercury_im.messenger.android.MercuryImApplication; +import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel; +import org.mercury_im.messenger.android.util.QrCodeGenerator; +import org.mercury_im.messenger.core.SchedulersFacade; +import org.mercury_im.messenger.core.util.Optional; +import org.mercury_im.messenger.core.viewmodel.ikey.IkeySecretKeyBackupCreationViewModel; + +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import io.reactivex.Observable; + +public class IkeyBackupCreationAndroidViewModel extends ViewModel implements MercuryAndroidViewModel { + + private static final Logger LOGGER = Logger.getLogger(IkeyBackupCreationAndroidViewModel.class.getName()); + + MutableLiveData passphrase = new MutableLiveData<>(); + MutableLiveData passphraseAsQrCode = new MutableLiveData<>(); + + @Inject + IkeySecretKeyBackupCreationViewModel commonViewModel; + + @Inject + SchedulersFacade schedulers; + + public IkeyBackupCreationAndroidViewModel() { + MercuryImApplication.getApplication().getAppComponent().inject(this); + } + + public void initialize(UUID accountId) { + getCommonViewModel().setAccountId(accountId); + + Observable> passphraseObservable = + //getCommonViewModel().getPassphrase() + Observable.just(new Optional<>(new OpenPgpSecretKeyBackupPassphrase("71ZA-Y416-UA7A-7NCE-3SNM-88EF"))); + + addDisposable(passphraseObservable + .subscribeOn(schedulers.getIoScheduler()) + .observeOn(schedulers.getUiScheduler()) + .filter(Optional::isPresent) + .map(Optional::getItem) + .subscribe( + passphrase::setValue, + e -> LOGGER.log(Level.SEVERE, "Error subscribing to passphrase", e))); + + addDisposable(passphraseObservable + .filter(Optional::isPresent) + .map(Optional::getItem) + .map(pass -> QrCodeGenerator.generateBarcode(pass.toString())) + .subscribeOn(schedulers.getIoScheduler()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(passphraseAsQrCode::setValue, + e -> LOGGER.log(Level.SEVERE, "Error subscribing to passphrase QR code", e))); + } + + public LiveData getPassphrase() { + return passphrase; + } + + public LiveData getPassphraseAsQrCode() { + return passphraseAsQrCode; + } + + @Override + public IkeySecretKeyBackupCreationViewModel getCommonViewModel() { + return commonViewModel; + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java new file mode 100644 index 0000000..502c52f --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupCreationFragment.java @@ -0,0 +1,62 @@ +package org.mercury_im.messenger.android.ui.ikey; + +import androidx.lifecycle.ViewModelProvider; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.mercury_im.messenger.R; + +import java.util.UUID; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class IkeyBackupCreationFragment extends Fragment { + + private IkeyBackupCreationAndroidViewModel viewModel; + + @BindView(R.id.backup_code) + TextView backupCode; + + @BindView(R.id.qr_code) + ImageView qrCode; + + private final UUID accountId; + + private IkeyBackupCreationFragment(UUID accountId) { + this.accountId = accountId; + } + + public static IkeyBackupCreationFragment newInstance(UUID accountId) { + return new IkeyBackupCreationFragment(accountId); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_ikey_backup_creation, container, false); + ButterKnife.bind(this, view); + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + viewModel = new ViewModelProvider(this).get(IkeyBackupCreationAndroidViewModel.class); + viewModel.initialize(accountId); + + viewModel.getPassphrase().observe(getViewLifecycleOwner(), passphrase -> backupCode.setText(passphrase)); + viewModel.getPassphraseAsQrCode().observe(getViewLifecycleOwner(), bitmap -> qrCode.setImageBitmap(bitmap)); + } + +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java b/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java new file mode 100644 index 0000000..2bb2f21 --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/ikey/IkeyBackupRestoreActivity.java @@ -0,0 +1,16 @@ +package org.mercury_im.messenger.android.ui.ikey; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; + +import org.mercury_im.messenger.R; + +public class IkeyBackupRestoreActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ikey_backup_restore); + } +} diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java b/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java index 7acd345..05e7340 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java @@ -42,7 +42,7 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter - - - - - - - \ No newline at end of file + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_ikey_backup_restore.xml b/app/src/main/res/layout/activity_ikey_backup_restore.xml new file mode 100644 index 0000000..4924064 --- /dev/null +++ b/app/src/main/res/layout/activity_ikey_backup_restore.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a5ba0cc..b53293b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,31 +14,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> - + - + app:layout_constraintBottom_toTopOf="@id/bottom_navigation" /> - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_account_details.xml b/app/src/main/res/layout/fragment_account_details.xml index 928e904..18f32dd 100644 --- a/app/src/main/res/layout/fragment_account_details.xml +++ b/app/src/main/res/layout/fragment_account_details.xml @@ -1,120 +1,188 @@ - - - - - - - - - + android:layout_height="wrap_content"> - - - + android:orientation="vertical"> - + + + + + + android:padding="12dp" + android:clipToPadding="false" + app:layout_constraintTop_toBottomOf="@id/jid"> + android:layout_gravity="center" + android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title" + android:text="Encryption Keys" /> - - + android:layout_marginBottom="8dp" + android:visibility="gone" + tools:visibility="visible" + app:cardCornerRadius="4dp" + app:cardElevation="4dp"> -