diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java index 510b4e405c7268ac9af019a140c83c16a6e5faab..db03b45906e5775e3541d15b605f3633b9436bcd 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java @@ -62,6 +62,7 @@ import com.fsck.k9.activity.compose.ComposeCryptoStatus; import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState; import com.fsck.k9.activity.compose.IdentityAdapter; import com.fsck.k9.activity.compose.IdentityAdapter.IdentityContainer; +import com.fsck.k9.activity.compose.OpenPgpConfigureCallBack; import com.fsck.k9.activity.compose.PgpEnabledErrorDialog.OnOpenPgpDisableListener; import com.fsck.k9.activity.compose.PgpInlineDialog.OnOpenPgpInlineChangeListener; import com.fsck.k9.activity.compose.PgpSignOnlyDialog.OnOpenPgpSignOnlyChangeListener; @@ -126,7 +127,8 @@ import timber.log.Timber; public class MessageCompose extends K9Activity implements OnClickListener, CancelListener, AttachmentDownloadCancelListener, OnFocusChangeListener, OnOpenPgpInlineChangeListener, OnOpenPgpSignOnlyChangeListener, MessageBuilder.Callback, - AttachmentPresenter.AttachmentsChangedListener, OnOpenPgpDisableListener, PermissionUiHelper { + AttachmentPresenter.AttachmentsChangedListener, OnOpenPgpDisableListener, PermissionUiHelper, + OpenPgpConfigureCallBack { private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1; private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 2; @@ -321,7 +323,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, recipientPresenter = new RecipientPresenter(getApplicationContext(), getSupportLoaderManager(), openPgpApiManager, recipientMvpView, account, composePgpInlineDecider, composePgpEnableByDefaultDecider, AutocryptStatusInteractor.getInstance(), new ReplyToParser(), - DI.get(AutocryptDraftStateHeaderParser.class)); + DI.get(AutocryptDraftStateHeaderParser.class), preferences); recipientPresenter.asyncUpdateCryptoStatus(); @@ -1455,6 +1457,11 @@ public class MessageCompose extends K9Activity implements OnClickListener, quotedMessagePresenter.processDraftMessage(messageViewInfo, k9identity); } + @Override + public void initializePgpSetup() { + recipientPresenter.initializeOpenPgpSetup(); + } + static class SendMessageTask extends AsyncTask { final MessagingController messagingController; final Preferences preferences; diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.kt index 5fcb1c64ac8825f79ebcb6a0e8f291a789b2c543..c646dd964f506243f5bc2c10555b4de012343acc 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.kt @@ -7,7 +7,6 @@ import com.fsck.k9.message.AutocryptStatusInteractor import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus import com.fsck.k9.message.CryptoStatus import com.fsck.k9.view.RecipientSelectView.Recipient -import org.openintents.openpgp.OpenPgpApiManager import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState /** This is an immutable object which contains all relevant metadata entered @@ -70,10 +69,10 @@ data class ComposeCryptoStatus( val recipientAddressesAsArray = recipientAddresses.toTypedArray() private val displayTypeFromProviderError = when (openPgpProviderState) { - OpenPgpApiManager.OpenPgpProviderState.OK -> null - OpenPgpApiManager.OpenPgpProviderState.UNCONFIGURED -> CryptoStatusDisplayType.UNCONFIGURED - OpenPgpApiManager.OpenPgpProviderState.UNINITIALIZED -> CryptoStatusDisplayType.UNINITIALIZED - OpenPgpApiManager.OpenPgpProviderState.ERROR, OpenPgpApiManager.OpenPgpProviderState.UI_REQUIRED -> CryptoStatusDisplayType.ERROR + OpenPgpProviderState.OK -> if(openPgpKeyId == null) CryptoStatusDisplayType.KEY_MISSING else null + OpenPgpProviderState.UNCONFIGURED -> CryptoStatusDisplayType.UNCONFIGURED + OpenPgpProviderState.UNINITIALIZED -> CryptoStatusDisplayType.UNINITIALIZED + OpenPgpProviderState.ERROR, OpenPgpProviderState.UI_REQUIRED -> CryptoStatusDisplayType.ERROR } private val displayTypeFromAutocryptError = when (recipientAutocryptStatusType) { diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/OpenPgpConfigureCallBack.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/OpenPgpConfigureCallBack.java new file mode 100644 index 0000000000000000000000000000000000000000..3b23077c45ba16312095913e45428317435f5eca --- /dev/null +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/OpenPgpConfigureCallBack.java @@ -0,0 +1,22 @@ +/* + * Copyright ECORP SAS 2022 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.fsck.k9.activity.compose; + +public interface OpenPgpConfigureCallBack { + + void initializePgpSetup(); +} diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/PgpKeySetupDialog.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/PgpKeySetupDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..d9fe12ee44f438fc7a03af801efbc517e59694ef --- /dev/null +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/PgpKeySetupDialog.java @@ -0,0 +1,68 @@ +/* + * Copyright ECORP SAS 2022 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.fsck.k9.activity.compose; + + +import android.annotation.SuppressLint; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.fsck.k9.ui.R; +import com.fsck.k9.view.HighlightDialogFragment; + + +public class PgpKeySetupDialog extends HighlightDialogFragment { + + public static PgpKeySetupDialog newInstance(@IdRes int showcaseView) { + final PgpKeySetupDialog dialog = new PgpKeySetupDialog(); + + final Bundle args = new Bundle(); + args.putInt(ARG_HIGHLIGHT_VIEW, showcaseView); + dialog.setArguments(args); + + return dialog; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + @SuppressLint("InflateParams") + final View view = LayoutInflater.from(getActivity()).inflate(R.layout.openpgp_setup_encryption_key_dialog, null); + + final Builder builder = new Builder(getActivity()) + .setView(view); + + builder.setPositiveButton(R.string.openpgp_dialog_create_key_pair, (dialog, which) -> { + if (!(getActivity() instanceof OpenPgpConfigureCallBack)) { + return; + } + + ((OpenPgpConfigureCallBack) getActivity()).initializePgpSetup(); + dialog.dismiss(); + }); + + builder.setNegativeButton(R.string.openpgp_dialog_cancel, (dialog, which) -> dialog.dismiss()); + + return builder.create(); + } +} diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/PgpUnconfiguredDialog.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/PgpUnconfiguredDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..f1a69c8662e844a052d8cde0f01c1fc5e4eb3658 --- /dev/null +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/PgpUnconfiguredDialog.java @@ -0,0 +1,68 @@ +/* + * Copyright ECORP SAS 2022 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.fsck.k9.activity.compose; + + +import android.annotation.SuppressLint; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.fsck.k9.ui.R; +import com.fsck.k9.view.HighlightDialogFragment; + + +public class PgpUnconfiguredDialog extends HighlightDialogFragment { + + public static PgpUnconfiguredDialog newInstance(@IdRes int showcaseView) { + final PgpUnconfiguredDialog dialog = new PgpUnconfiguredDialog(); + + final Bundle args = new Bundle(); + args.putInt(ARG_HIGHLIGHT_VIEW, showcaseView); + dialog.setArguments(args); + + return dialog; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + @SuppressLint("InflateParams") + final View view = LayoutInflater.from(getActivity()).inflate(R.layout.openpgp_enable_encryption_dialog, null); + + final Builder builder = new Builder(getActivity()) + .setView(view); + + builder.setPositiveButton(R.string.openpgp_dialog_proceed, (dialog, which) -> { + if (!(getActivity() instanceof OpenPgpConfigureCallBack)) { + return; + } + + ((OpenPgpConfigureCallBack) getActivity()).initializePgpSetup(); + dialog.dismiss(); + }); + + builder.setNegativeButton(R.string.openpgp_dialog_cancel, (dialog, which) -> dialog.dismiss()); + + return builder.create(); + } +} diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt index 60f5b84f62b902e206980874e5ccff7962e1b9fc..9e200899487c540d588a95f239c9381063db30e5 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.kt @@ -371,6 +371,16 @@ class RecipientMvpView(private val activity: MessageCompose) : View.OnFocusChang dialog.show(activity.supportFragmentManager, "openpgp_description") } + fun showOpenPgpUncofiguredDialog() { + val dialog = PgpUnconfiguredDialog.newInstance(R.id.crypto_status_anchor) + dialog.show(activity.supportFragmentManager, "openpgp_unconfigure") + } + + fun showOpenPgpUserKeySetupDialog() { + val dialog = PgpKeySetupDialog.newInstance(R.id.crypto_status_anchor) + dialog.show(activity.supportFragmentManager, "openpgp_key_setup") + } + fun launchUserInteractionPendingIntent(pendingIntent: PendingIntent?, requestCode: Int) { activity.launchUserInteractionPendingIntent(pendingIntent, requestCode) } @@ -382,15 +392,16 @@ class RecipientMvpView(private val activity: MessageCompose) : View.OnFocusChang } enum class CryptoStatusDisplayType(val childIdToDisplay: Int) { - UNCONFIGURED(VIEW_INDEX_HIDDEN), + UNCONFIGURED(R.id.crypto_status_disabled), UNINITIALIZED(VIEW_INDEX_HIDDEN), SIGN_ONLY(R.id.crypto_status_disabled), - UNAVAILABLE(VIEW_INDEX_HIDDEN), + UNAVAILABLE(R.id.crypto_status_disabled), ENABLED(R.id.crypto_status_enabled), ENABLED_ERROR(R.id.crypto_status_error), ENABLED_TRUSTED(R.id.crypto_status_trusted), AVAILABLE(R.id.crypto_status_disabled), - ERROR(R.id.crypto_status_error); + ERROR(R.id.crypto_status_error), + KEY_MISSING(R.id.crypto_status_key_missing); } enum class CryptoSpecialModeDisplayType(val childIdToDisplay: Int) { diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt index 9b0e6d2ed35582adb08a7e13c8c53adeb47665c3..03bd933f13694969d4a4a9801871c457b32dcdb4 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.kt @@ -2,6 +2,7 @@ package com.fsck.k9.activity.compose import android.Manifest import android.app.Activity +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -14,10 +15,12 @@ import androidx.loader.app.LoaderManager import com.fsck.k9.Account import com.fsck.k9.Identity import com.fsck.k9.K9 +import com.fsck.k9.Preferences import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState import com.fsck.k9.autocrypt.AutocryptDraftStateHeader import com.fsck.k9.autocrypt.AutocryptDraftStateHeaderParser +import com.fsck.k9.crypto.OpenPgpApiHelper import com.fsck.k9.helper.Contacts import com.fsck.k9.helper.MailTo import com.fsck.k9.helper.ReplyToParser @@ -37,6 +40,8 @@ import org.openintents.openpgp.OpenPgpApiManager import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState +import org.openintents.openpgp.util.OpenPgpKeySetupUtil +import org.openintents.openpgp.util.OpenPgpProviderUtil import timber.log.Timber private const val STATE_KEY_CC_SHOWN = "state:ccShown" @@ -63,7 +68,8 @@ class RecipientPresenter( private val composePgpEnableByDefaultDecider: ComposePgpEnableByDefaultDecider, private val autocryptStatusInteractor: AutocryptStatusInteractor, private val replyToParser: ReplyToParser, - private val draftStateHeaderParser: AutocryptDraftStateHeaderParser + private val draftStateHeaderParser: AutocryptDraftStateHeaderParser, + private val preferences: Preferences ) { private lateinit var account: Account private var alwaysBccAddresses: Array
? = null @@ -91,6 +97,8 @@ class RecipientPresenter( private val allRecipients: List get() = with(recipientMvpView) { toRecipients + ccRecipients + bccRecipients } + private var openPgpKeySetupUtil : OpenPgpKeySetupUtil? = null + init { recipientMvpView.setPresenter(this) recipientMvpView.setLoaderManager(loaderManager) @@ -460,6 +468,11 @@ class RecipientPresenter( } fun onCryptoModeChanged(cryptoMode: CryptoMode) { + if (cryptoMode == CryptoMode.CHOICE_ENABLED && isInPgpKeySetupState()) { + recipientMvpView.showOpenPgpUserKeySetupDialog() + return + } + currentCryptoMode = cryptoMode asyncUpdateCryptoStatus() } @@ -517,6 +530,10 @@ class RecipientPresenter( } fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (openPgpKeySetupUtil != null && openPgpKeySetupUtil?.handleOnActivityResult(requestCode, resultCode, data) == true) { + return + } + when (requestCode) { CONTACT_PICKER_TO, CONTACT_PICKER_CC, CONTACT_PICKER_BCC -> { if (resultCode != Activity.RESULT_OK || data == null) return @@ -526,6 +543,7 @@ class RecipientPresenter( } OPENPGP_USER_INTERACTION -> { openPgpApiManager.onUserInteractionResult() + redrawCachedCryptoStatusIcon() } REQUEST_CODE_AUTOCRYPT -> { asyncUpdateCryptoStatus() @@ -540,9 +558,10 @@ class RecipientPresenter( } fun onClickCryptoStatus() { + when (openPgpApiManager.openPgpProviderState) { OpenPgpProviderState.UNCONFIGURED -> { - Timber.e("click on crypto status while unconfigured - this should not really happen?!") + recipientMvpView.showOpenPgpUncofiguredDialog() } OpenPgpProviderState.OK -> { toggleEncryptionState(false) @@ -558,6 +577,77 @@ class RecipientPresenter( } } + + fun initializeOpenPgpSetup() { + if (openPgpApiManager.openPgpProviderState == OpenPgpProviderState.UNCONFIGURED) { + configureOpenPgp() + } else if (isInPgpKeySetupState()) { + initializeUserPgpKeySetup() + } + } + + private fun configureOpenPgp() { + setUpPgpProvider() + + if(account.openPgpProvider == null) { + return + } + + recipientMvpView.setCryptoProvider(account.openPgpProvider) + openPgpApiManager.setOpenPgpProvider(account.openPgpProvider, object : OpenPgpApiManagerCallback { + override fun onOpenPgpProviderStatusChanged() { + if (openPgpApiManager.openPgpProviderState == OpenPgpProviderState.UI_REQUIRED) { + val pendingIntent = openPgpApiManager.userInteractionPendingIntent + recipientMvpView.launchUserInteractionPendingIntent(pendingIntent, OPENPGP_USER_INTERACTION) + return + } + + if (isInPgpKeySetupState()) { + initializeUserPgpKeySetup() + return + } + + asyncUpdateCryptoStatus() + } + + override fun onOpenPgpProviderError(error: OpenPgpProviderError) { + openPgpCallback.onOpenPgpProviderError(error) + } + }) + } + + private fun isInPgpKeySetupState() = (openPgpApiManager.openPgpProviderState == OpenPgpProviderState.OK && account.openPgpKey == Account.NO_OPENPGP_KEY) + + private fun setUpPgpProvider() { + if (account.openPgpProvider == null) { + account.openPgpProvider = OpenPgpProviderUtil.getOpenPgpProviderPackage(context) + preferences.saveAccount(account) + } + } + + private fun initializeUserPgpKeySetup() { + if (!isInPgpKeySetupState()) { + return + } + val userId = OpenPgpApiHelper.buildUserId(account.getIdentity(0)) + openPgpKeySetupUtil = OpenPgpKeySetupUtil(openPgpApiManager, account.openPgpProvider, userId, true) + openPgpKeySetupUtil?.registerCallBack(object : OpenPgpKeySetupUtil.OpenPgpKeySetupCallBack { + override fun startPendingIntentForResult(pendingIntent: PendingIntent?, requestCode: Int) { + recipientMvpView.launchUserInteractionPendingIntent(pendingIntent, requestCode) + } + + override fun saveKeyId(key: Long) { + account.openPgpKey = key + preferences.saveAccount(account) + openPgpKeySetupUtil = null + openPgpApiManager.onUserInteractionResult() + asyncUpdateCryptoStatus() + } + }) + + openPgpKeySetupUtil?.startSetup() + } + private fun toggleEncryptionState(showGotIt: Boolean) { val currentCryptoStatus = currentCachedCryptoStatus if (currentCryptoStatus == null) { @@ -622,7 +712,7 @@ class RecipientPresenter( when (sendErrorState) { SendErrorState.ENABLED_ERROR -> recipientMvpView.showOpenPgpEnabledErrorDialog(false) SendErrorState.PROVIDER_ERROR -> recipientMvpView.showErrorOpenPgpConnection() - SendErrorState.KEY_CONFIG_ERROR -> recipientMvpView.showErrorNoKeyConfigured() + SendErrorState.KEY_CONFIG_ERROR -> recipientMvpView.showOpenPgpUserKeySetupDialog() else -> throw AssertionError("not all error states handled, this is a bug!") } } diff --git a/app/ui/legacy/src/main/res/drawable/status_lock_error_open.xml b/app/ui/legacy/src/main/res/drawable/status_lock_error_open.xml new file mode 100644 index 0000000000000000000000000000000000000000..625d4129c218195b0ea645b969482ef4b21c50ba --- /dev/null +++ b/app/ui/legacy/src/main/res/drawable/status_lock_error_open.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/ui/legacy/src/main/res/layout/message_compose_recipients.xml b/app/ui/legacy/src/main/res/layout/message_compose_recipients.xml index 2d93d6e2371c03586f4270f185fe160115ef97eb..dea72da38d0abc6397faedd0d4136f70902333f7 100644 --- a/app/ui/legacy/src/main/res/layout/message_compose_recipients.xml +++ b/app/ui/legacy/src/main/res/layout/message_compose_recipients.xml @@ -154,6 +154,14 @@ app:srcCompat="@drawable/status_lock_error" app:tint="?attr/openpgp_red" /> + + diff --git a/app/ui/legacy/src/main/res/layout/openpgp_enable_encryption_dialog.xml b/app/ui/legacy/src/main/res/layout/openpgp_enable_encryption_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..e3c4048b7fbe88972c07bad28c8891695010da62 --- /dev/null +++ b/app/ui/legacy/src/main/res/layout/openpgp_enable_encryption_dialog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/app/ui/legacy/src/main/res/layout/openpgp_setup_encryption_key_dialog.xml b/app/ui/legacy/src/main/res/layout/openpgp_setup_encryption_key_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..44d9d428c0006460efcc5bbd926eec8090f4d8eb --- /dev/null +++ b/app/ui/legacy/src/main/res/layout/openpgp_setup_encryption_key_dialog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/app/ui/legacy/src/main/res/values/strings.xml b/app/ui/legacy/src/main/res/values/strings.xml index a5575231c2013c91a63f41a99aa3e9f9ee6ef057..fba9354fb7dfd69b4f072e12e31f372ec609a068 100644 --- a/app/ui/legacy/src/main/res/values/strings.xml +++ b/app/ui/legacy/src/main/res/values/strings.xml @@ -1094,6 +1094,9 @@ Signatures may break when sent to mailing lists. Signatures may be displayed as \'signature.asc\' attachments in some clients. Encrypted messages always include a signature. + PROCEED + CANCEL + CREATE KEY PAIR Plaintext end-to-end signature contained an error @@ -1171,6 +1174,10 @@ Back Disable Encryption OpenPGP Encryption + Enable encryption + End-to-end encryption is the safest way to keep other people from spying on communications or accessing data transferred from one end device to another.By proceeding, OpenPGP support will be enabled.\n\nRequired: To send an encrypted mail, a pair of keys is required so that the data encrypted on the sender\'s device can be decrypted by the intended recipient. For example, you will need the public key of the person you are sending to and vice versa. + Set up encryption keys + End-to-end encryption is the safest way to keep other people from spying on communications or accessing data transferred from one end device to another.\n\nTo send an encrypted mail, a pair of keys is required so that the data encrypted on the sender\'s device can be decrypted by the intended recipient. For example, you will need the public key of the person you are sending to and vice versa. Autocrypt mutual mode Autocrypt mutual mode diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeySetupUtil.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeySetupUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d59150452252f64dc038c5e593f71eab62c44b21 --- /dev/null +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeySetupUtil.java @@ -0,0 +1,236 @@ +/* + * Copyright ECORP SAS 2022 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.openintents.openpgp.util; + + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentSender; + +import org.openintents.openpgp.OpenPgpApiManager; +import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback; +import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError; +import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState; +import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; +import timber.log.Timber; + + +public class OpenPgpKeySetupUtil implements OpenPgpApiManagerCallback { + + private static final int NO_KEY = 0; + private static final int REQUEST_CODE_API_MANAGER = 6; + private static final int REQUEST_CODE_KEY_PREFERENCE = 7; + + private final boolean showAutocryptHint; + private final String userId; + private final OpenPgpApiManager openPgpApiManager; + + private long keyId; + private boolean pendingIntentRunImmediately; + private Intent cachedActivityResultData; + private PendingIntent pendingIntent; + private OpenPgpKeySetupCallBack callBack; + + public OpenPgpKeySetupUtil(OpenPgpApiManager openPgpApiManager, String openPgpProvider, String userId, boolean showAutocryptHint) { + this.openPgpApiManager = openPgpApiManager; + this.openPgpApiManager.setOpenPgpProvider(openPgpProvider, this); + this.userId = userId; + this.showAutocryptHint = showAutocryptHint; + } + + public void registerCallBack(OpenPgpKeySetupCallBack callBack) { + this.callBack = callBack; + } + + public void startSetup() { + switch (openPgpApiManager.getOpenPgpProviderState()) { + // The GET_SIGN_KEY action is special, in that it can be used as an implicit registration + // to the API. Therefore, we can ignore the UI_REQUIRED here. If it comes up regardless, + // it will also work as a regular pending intent. + case UI_REQUIRED: + case OK: { + initializePendingIntent(); + break; + } + default: { + openPgpApiManager.refreshConnection(); + break; + } + } + } + + @Override + public void onOpenPgpProviderStatusChanged() { + if (openPgpApiManager.getOpenPgpProviderState() == OpenPgpProviderState.OK) { + retrievePendingIntentInfo(); + } else { + pendingIntent = null; + pendingIntentRunImmediately = false; + cachedActivityResultData = null; + } + } + + @Override + public void onOpenPgpProviderError(OpenPgpProviderError error) { + if (error == OpenPgpProviderError.ConnectionLost) { + openPgpApiManager.refreshConnection(); + } + } + + private void retrievePendingIntentInfo() { + Intent data; + + if (cachedActivityResultData != null) { + data = cachedActivityResultData; + cachedActivityResultData = null; + } else { + data = new Intent(); + } + + retrievePendingIntentInfo(data); + } + + private void retrievePendingIntentInfo(Intent data) { + data.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); + data.putExtra(OpenPgpApi.EXTRA_USER_ID, userId); + data.putExtra(OpenPgpApi.EXTRA_PRESELECT_KEY_ID, keyId); + data.putExtra(OpenPgpApi.EXTRA_SHOW_AUTOCRYPT_HINT, showAutocryptHint); + OpenPgpApi api = openPgpApiManager.getOpenPgpApi(); + api.executeApiAsync(data, null, null, openPgpCallback); + } + + private final IOpenPgpCallback openPgpCallback = result -> { + int resultCode = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); + switch (resultCode) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: { + PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); + + if (result.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + long keyId = result.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, NO_KEY); + + updateData(keyId, pendingIntent); + } else { + updateData(pendingIntent); + } + + break; + } + + case OpenPgpApi.RESULT_CODE_ERROR: { + OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + Timber.e("RESULT_CODE_ERROR: %s", error.getMessage()); + break; + } + } + }; + + /** + * if pendingIntent present, then start it, else retrieve pendingIntent info by calling openPgpApi + */ + private void initializePendingIntent() { + if (pendingIntent != null) { + startPendingIntent(); + return; + } + + pendingIntentRunImmediately = true; + retrievePendingIntentInfo(); + } + + private void startPendingIntent() { + if (pendingIntent == null) { + Timber.e("Tried to launch pending intent but didn't have any?"); + return; + } + + try { + if (callBack != null) { + callBack.startPendingIntentForResult(pendingIntent, REQUEST_CODE_KEY_PREFERENCE); + } + } catch (IntentSender.SendIntentException e) { + Timber.e(e,"Error launching pending intent"); + } finally { + pendingIntent = null; + } + } + + private void updateData(PendingIntent pendingIntent) { + this.pendingIntent = pendingIntent; + maybeRunPendingIntentImmediately(); + } + + private void updateData(long keyId, PendingIntent pendingIntent) { + setKeyId(keyId); + this.pendingIntent = pendingIntent; + + maybeRunPendingIntentImmediately(); + } + + private void maybeRunPendingIntentImmediately() { + if (!pendingIntentRunImmediately) { + return; + } + + pendingIntentRunImmediately = false; + startPendingIntent(); + } + + private void setKeyId(long newValue) { + if (newValue == 0L) { + return; + } + + keyId = newValue; + + if (callBack != null) { + callBack.saveKeyId(keyId); + } + } + + public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_API_MANAGER: + openPgpApiManager.onUserInteractionResult(); + return true; + + case REQUEST_CODE_KEY_PREFERENCE: + if (resultCode == Activity.RESULT_OK) { + cachedActivityResultData = data; + // this might happen early in the lifecycle (e.g. before onResume). if the provider isn't connected + // here, apiRetrievePendingIntentAndKeyInfo() will be called as soon as it is. + OpenPgpProviderState openPgpProviderState = openPgpApiManager.getOpenPgpProviderState(); + + if (openPgpProviderState == OpenPgpProviderState.OK || openPgpProviderState == OpenPgpProviderState.UI_REQUIRED) { + retrievePendingIntentInfo(); + } + } + return true; + } + return false; + } + + + public interface OpenPgpKeySetupCallBack { + + void startPendingIntentForResult(PendingIntent pendingIntent, int requestCode) throws IntentSender.SendIntentException; + + void saveKeyId(long key); + } +} diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java index b5e9b1e802d4fd6df64e8730c1e84b596f402114..c2a30e9387e200955ac7b4f34eb900f09abca720 100644 --- a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java @@ -9,6 +9,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import androidx.annotation.Nullable; + public class OpenPgpProviderUtil { private static final String PACKAGE_NAME_APG = "org.thialfihar.android.apg"; @@ -59,4 +61,13 @@ public class OpenPgpProviderUtil { public static boolean isProviderAllowed(String packageName) { return !DISALLOWED_PROVIDERS.contains(packageName); } + + @Nullable + public static String getOpenPgpProviderPackage(Context context) { + final List openPgpProviderPackages = getOpenPgpProviderPackages(context); + if (openPgpProviderPackages.size() > 0) { + return openPgpProviderPackages.get(0); + } + return null; + } }