diff --git a/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java b/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java index 7be13f1fe..5c6c8ed4f 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java @@ -109,8 +109,11 @@ import org.htmlcleaner.CleanerProperties; import org.htmlcleaner.HtmlCleaner; import org.htmlcleaner.SimpleHtmlSerializer; import org.htmlcleaner.TagNode; +import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; +import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound; + @SuppressWarnings("deprecation") public class MessageCompose extends K9Activity implements OnClickListener, @@ -695,7 +698,17 @@ public class MessageCompose extends K9Activity implements OnClickListener, // attachKeyCheckBox = (CheckBox) findViewById(R.id.cb_attach_key); // attachKeyCheckBox.setEnabled(mAccount.getCryptoKey() != 0); - mOpenPgpServiceConnection = new OpenPgpServiceConnection(this, mOpenPgpProvider); + mOpenPgpServiceConnection = new OpenPgpServiceConnection(this, mOpenPgpProvider, new OnBound() { + @Override + public void onBound(IOpenPgpService2 service) { + recipientPresenter.onCryptoProviderBound(); + } + + @Override + public void onError(Exception e) { + recipientPresenter.onCryptoProviderError(e); + } + }); mOpenPgpServiceConnection.bindToService(); updateMessageFormat(); @@ -1320,16 +1333,17 @@ public class MessageCompose extends K9Activity implements OnClickListener, return; } - if (resultCode != RESULT_OK) { - return; - } - if (data == null) { + if ((requestCode & REQUEST_MASK_RECIPIENT_PRESENTER) == REQUEST_MASK_RECIPIENT_PRESENTER) { + requestCode ^= REQUEST_MASK_RECIPIENT_PRESENTER; + recipientPresenter.onActivityResult(resultCode, requestCode, data); return; } - if ((requestCode & REQUEST_MASK_RECIPIENT_PRESENTER) == REQUEST_MASK_RECIPIENT_PRESENTER) { - requestCode ^= REQUEST_MASK_RECIPIENT_PRESENTER; - recipientPresenter.onActivityResult(requestCode, data); + if (resultCode != RESULT_OK) { + return; + } + + if (data == null) { return; } @@ -3015,4 +3029,12 @@ public class MessageCompose extends K9Activity implements OnClickListener, } } + public void launchUserInteractionPendingIntent(PendingIntent pendingIntent, int requestCode) { + requestCode |= REQUEST_MASK_RECIPIENT_PRESENTER; + try { + startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0); + } catch (SendIntentException e) { + e.printStackTrace(); + } + } } diff --git a/k9mail/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.java b/k9mail/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.java index de4937278..8fa4011da 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/compose/ComposeCryptoStatus.java @@ -65,14 +65,23 @@ public class ComposeCryptoStatus { return CryptoStatusDisplayType.OPPORTUNISTIC_NOKEY; case SIGN_ONLY: return CryptoStatusDisplayType.SIGN_ONLY; - default: case DISABLE: return CryptoStatusDisplayType.DISABLED; + case ERROR: + return CryptoStatusDisplayType.ERROR; + default: + case UNINITIALIZED: + return CryptoStatusDisplayType.UNINITIALIZED; } } + public boolean isPgpErrorState() { + return cryptoMode == CryptoMode.ERROR; + } + public boolean shouldUsePgpMessageBuilder() { - return cryptoMode != CryptoMode.DISABLE; + return cryptoMode == CryptoMode.PRIVATE || cryptoMode == CryptoMode.OPPORTUNISTIC + || cryptoMode == CryptoMode.SIGN_ONLY; } public boolean isEncryptionEnabled() { @@ -129,7 +138,7 @@ public class ComposeCryptoStatus { ArrayList keyReferences = new ArrayList<>(); boolean allKeysAvailable = true; - boolean allKeysVerified = true; + boolean allKeysVerified = !recipients.isEmpty(); for (Recipient recipient : recipients) { RecipientCryptoStatus cryptoStatus = recipient.getCryptoStatus(); if (cryptoStatus.isAvailable()) { diff --git a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientLoader.java b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientLoader.java index f7cc5a225..b3797efe7 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientLoader.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientLoader.java @@ -255,9 +255,14 @@ public class RecipientLoader extends AsyncTaskLoader> { List recipientList = new ArrayList<>(recipientMap.keySet()); String[] recipientAddresses = recipientList.toArray(new String[recipientList.size()]); + Cursor cursor; Uri queryUri = Uri.parse("content://" + cryptoProvider + ".provider.exported/email_status"); - Cursor cursor = getContext().getContentResolver().query(queryUri, PROJECTION_CRYPTO_STATUS, null, - recipientAddresses, null); + try { + cursor = getContext().getContentResolver().query(queryUri, PROJECTION_CRYPTO_STATUS, null, + recipientAddresses, null); + } catch (SecurityException e) { + return; + } initializeCryptoStatusForAllRecipients(recipientMap); diff --git a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.java b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.java index 4e4fb7bd9..58aa0289f 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientMvpView.java @@ -6,6 +6,7 @@ import java.util.List; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.app.PendingIntent; import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; @@ -25,11 +26,13 @@ import com.fsck.k9.view.RecipientSelectView.TokenListener; public class RecipientMvpView implements OnFocusChangeListener, OnClickListener { + private static final int VIEW_INDEX_HIDDEN = -1; private static final int VIEW_INDEX_CRYPTO_STATUS_DISABLED = 0; - private static final int VIEW_INDEX_CRYPTO_STATUS_NO_KEY = 1; - private static final int VIEW_INDEX_CRYPTO_STATUS_UNTRUSTED = 2; - private static final int VIEW_INDEX_CRYPTO_STATUS_TRUSTED = 3; - private static final int VIEW_INDEX_CRYPTO_STATUS_SIGN_ONLY = 4; + private static final int VIEW_INDEX_CRYPTO_STATUS_ERROR = 1; + private static final int VIEW_INDEX_CRYPTO_STATUS_NO_KEY = 2; + private static final int VIEW_INDEX_CRYPTO_STATUS_UNTRUSTED = 3; + private static final int VIEW_INDEX_CRYPTO_STATUS_TRUSTED = 4; + private static final int VIEW_INDEX_CRYPTO_STATUS_SIGN_ONLY = 5; private static final int VIEW_INDEX_BCC_EXPANDER_VISIBLE = 0; private static final int VIEW_INDEX_BCC_EXPANDER_HIDDEN = 1; @@ -223,20 +226,6 @@ public class RecipientMvpView implements OnFocusChangeListener, OnClickListener return bccView.getObjects(); } - public void hideCryptoStatus() { - if (cryptoStatusView.getVisibility() == View.GONE) { - return; - } - - cryptoStatusView.animate().translationX(100).setDuration(300).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - cryptoStatusView.setVisibility(View.GONE); - } - }).start(); - } - public boolean recipientToHasUncompletedText() { return toView.hasUncompletedText(); } @@ -261,17 +250,16 @@ public class RecipientMvpView implements OnFocusChangeListener, OnClickListener bccView.setError(bccView.getContext().getString(R.string.compose_error_incomplete_recipient)); } - public void showMissingSignKeyError() { - Toast.makeText(activity, R.string.compose_error_no_signing_key, Toast.LENGTH_LONG).show(); - } + public void showCryptoStatus(final CryptoStatusDisplayType cryptoStatusDisplayType) { + boolean shouldBeHidden = cryptoStatusDisplayType.childToDisplay == VIEW_INDEX_HIDDEN; + if (shouldBeHidden) { + hideCryptoStatus(); + return; + } - public void showPrivateAndIncompleteError() { - Toast.makeText(activity, R.string.compose_error_private_missing_keys, Toast.LENGTH_LONG).show(); - } - - public void showCryptoStatus(final CryptoStatusDisplayType childToDisplay) { - if (cryptoStatusView.getVisibility() == View.VISIBLE) { - switchCryptoStatus(childToDisplay); + boolean alreadyVisible = cryptoStatusView.getVisibility() == View.VISIBLE; + if (alreadyVisible) { + switchCryptoStatus(cryptoStatusDisplayType); return; } @@ -280,7 +268,22 @@ public class RecipientMvpView implements OnFocusChangeListener, OnClickListener cryptoStatusView.animate().translationX(0).setDuration(300).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - switchCryptoStatus(childToDisplay); + switchCryptoStatus(cryptoStatusDisplayType); + } + }).start(); + } + + + private void hideCryptoStatus() { + if (cryptoStatusView.getVisibility() == View.GONE) { + return; + } + + cryptoStatusView.animate().translationX(100).setDuration(300).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + cryptoStatusView.setVisibility(View.GONE); } }).start(); } @@ -297,8 +300,23 @@ public class RecipientMvpView implements OnFocusChangeListener, OnClickListener } public void showErrorContactNoAddress() { - String errorMessage = activity.getString(R.string.error_contact_address_not_found); - Toast.makeText(activity, errorMessage, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.error_contact_address_not_found, Toast.LENGTH_LONG).show(); + } + + public void showErrorOpenPgpConnection() { + Toast.makeText(activity, R.string.error_crypto_provider_connect, Toast.LENGTH_LONG).show(); + } + + public void showErrorOpenPgpUserInteractionRequired() { + Toast.makeText(activity, R.string.error_crypto_provider_ui_required, Toast.LENGTH_LONG).show(); + } + + public void showErrorMissingSignKey() { + Toast.makeText(activity, R.string.compose_error_no_signing_key, Toast.LENGTH_LONG).show(); + } + + public void showErrorPrivateButMissingKeys() { + Toast.makeText(activity, R.string.compose_error_private_missing_keys, Toast.LENGTH_LONG).show(); } @Override @@ -342,7 +360,16 @@ public class RecipientMvpView implements OnFocusChangeListener, OnClickListener dialog.show(activity.getFragmentManager(), "crypto_settings"); } + public void checkOpenPgpServicePermission(RecipientPresenter recipientPresenter) { + activity.getOpenPgpApi().checkPermissionPing(recipientPresenter); + } + + public void launchUserInteractionPendingIntent(PendingIntent pendingIntent, int requestCode) { + activity.launchUserInteractionPendingIntent(pendingIntent, requestCode); + } + public enum CryptoStatusDisplayType { + UNINITIALIZED(VIEW_INDEX_HIDDEN), DISABLED(VIEW_INDEX_CRYPTO_STATUS_DISABLED), SIGN_ONLY(VIEW_INDEX_CRYPTO_STATUS_SIGN_ONLY), OPPORTUNISTIC_NOKEY(VIEW_INDEX_CRYPTO_STATUS_NO_KEY), @@ -350,7 +377,8 @@ public class RecipientMvpView implements OnFocusChangeListener, OnClickListener OPPORTUNISTIC_TRUSTED(VIEW_INDEX_CRYPTO_STATUS_TRUSTED), PRIVATE_NOKEY(VIEW_INDEX_CRYPTO_STATUS_NO_KEY), PRIVATE_UNTRUSTED(VIEW_INDEX_CRYPTO_STATUS_UNTRUSTED), - PRIVATE_TRUSTED(VIEW_INDEX_CRYPTO_STATUS_TRUSTED); + PRIVATE_TRUSTED(VIEW_INDEX_CRYPTO_STATUS_TRUSTED), + ERROR(VIEW_INDEX_CRYPTO_STATUS_ERROR); final int childToDisplay; diff --git a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.java b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.java index 1cdadb27a..4e5561117 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientPresenter.java @@ -6,17 +6,22 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import android.app.Activity; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import com.fsck.k9.Account; import com.fsck.k9.Identity; +import com.fsck.k9.K9; import com.fsck.k9.R; +import com.fsck.k9.activity.MessageCompose; import com.fsck.k9.activity.compose.ComposeCryptoStatus.ComposeCryptoStatusBuilder; import com.fsck.k9.helper.Contacts; import com.fsck.k9.helper.MailTo; @@ -27,15 +32,18 @@ import com.fsck.k9.mail.Message.RecipientType; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.view.RecipientSelectView.Recipient; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.PermissionPingCallback; -public class RecipientPresenter { +public class RecipientPresenter implements PermissionPingCallback { private static final String STATE_KEY_CC_SHOWN = "state:ccShown"; private static final String STATE_KEY_BCC_SHOWN = "state:bccShown"; private static final int CONTACT_PICKER_TO = 1; private static final int CONTACT_PICKER_CC = 2; private static final int CONTACT_PICKER_BCC = 3; + private static final int OPENPGP_USER_INTERACTION = 4; private final Context context; @@ -44,8 +52,9 @@ public class RecipientPresenter { private String cryptoProvider; private RecipientType lastFocusedType = RecipientType.TO; private Boolean hasContactPicker; - private CryptoMode currentCryptoMode = CryptoMode.OPPORTUNISTIC; + private CryptoMode currentCryptoMode = CryptoMode.UNINITIALIZED; private ComposeCryptoStatus currentCryptoStatus; + private PendingIntent pendingUserInteractionIntent; public RecipientPresenter(Context context, RecipientMvpView recipientMvpView, Account account) { @@ -301,8 +310,6 @@ public class RecipientPresenter { } public void updateCryptoStatus() { - List recipients = getAllRecipients(); - ComposeCryptoStatusBuilder builder = new ComposeCryptoStatusBuilder() .setCryptoMode(currentCryptoMode) .setRecipients(getAllRecipients()); @@ -315,13 +322,6 @@ public class RecipientPresenter { } currentCryptoStatus = builder.build(); - - boolean hasNoCryptoProviderOrRecipients = cryptoProvider == null || recipients.isEmpty(); - if (hasNoCryptoProviderOrRecipients) { - recipientMvpView.hideCryptoStatus(); - return; - } - recipientMvpView.showCryptoStatus(currentCryptoStatus.getCryptoStatusDisplayType()); } @@ -438,13 +438,21 @@ public class RecipientPresenter { recipientMvpView.showContactPicker(requestCode); } - public void onActivityResult(int requestCode, Intent data) { - if (requestCode != CONTACT_PICKER_TO && requestCode != CONTACT_PICKER_CC && requestCode != CONTACT_PICKER_BCC) { - return; + public void onActivityResult(int resultCode, int requestCode, Intent data) { + switch (requestCode) { + case CONTACT_PICKER_TO: + case CONTACT_PICKER_CC: + case CONTACT_PICKER_BCC: + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + RecipientType recipientType = recipientTypeFromRequestCode(requestCode); + addRecipientFromContactUri(recipientType, data.getData()); + break; + case OPENPGP_USER_INTERACTION: + recipientMvpView.checkOpenPgpServicePermission(this); + break; } - - RecipientType recipientType = recipientTypeFromRequestCode(requestCode); - addRecipientFromContactUri(recipientType, data.getData()); } private static int recipientTypeToRequestCode(RecipientType type) { @@ -482,6 +490,16 @@ public class RecipientPresenter { } public void onClickCryptoStatus() { + if (currentCryptoMode == CryptoMode.ERROR) { + if (pendingUserInteractionIntent != null) { + recipientMvpView + .launchUserInteractionPendingIntent(pendingUserInteractionIntent, OPENPGP_USER_INTERACTION); + pendingUserInteractionIntent = null; + } else { + recipientMvpView.checkOpenPgpServicePermission(this); + } + return; + } recipientMvpView.showCryptoDialog(currentCryptoMode); } @@ -510,22 +528,67 @@ public class RecipientPresenter { } ComposeCryptoStatus cryptoStatus = getCurrentCryptoStatus(); + if (cryptoStatus.isPgpErrorState()) { + // TODO: be more specific about this error + recipientMvpView.showErrorOpenPgpConnection(); + return false; + } if (cryptoStatus.isMissingSignKey()) { - recipientMvpView.showMissingSignKeyError(); + recipientMvpView.showErrorMissingSignKey(); return false; } if (cryptoStatus.isPrivateAndIncomplete()) { - recipientMvpView.showPrivateAndIncompleteError(); + recipientMvpView.showErrorPrivateButMissingKeys(); return false; } return true; } + public void onCryptoProviderBound() { + recipientMvpView.checkOpenPgpServicePermission(this); + } + + public void onCryptoProviderError(Exception e) { + // TODO handle error case better + recipientMvpView.showErrorOpenPgpConnection(); + currentCryptoMode = CryptoMode.ERROR; + Log.e(K9.LOG_TAG, "error connecting to crypto provider!", e); + updateCryptoStatus(); + } + + @Override + public void onPgpPermissionCheckResult(Intent result) { + int resultCode = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); + switch (resultCode) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + if (currentCryptoMode == CryptoMode.UNINITIALIZED || currentCryptoMode == CryptoMode.ERROR) { + // TODO initialize to other values under some circumstances, e.g. if we reply to an encrypted e-mail + currentCryptoMode = CryptoMode.OPPORTUNISTIC; + } + break; + + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + recipientMvpView.showErrorOpenPgpUserInteractionRequired(); + pendingUserInteractionIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); + currentCryptoMode = CryptoMode.ERROR; + break; + + case OpenPgpApi.RESULT_CODE_ERROR: + default: + recipientMvpView.showErrorOpenPgpConnection(); + currentCryptoMode = CryptoMode.ERROR; + break; + } + updateCryptoStatus(); + } + public enum CryptoMode { - PRIVATE, - OPPORTUNISTIC, - SIGN_ONLY, + UNINITIALIZED, DISABLE, + SIGN_ONLY, + OPPORTUNISTIC, + PRIVATE, + ERROR, } } diff --git a/k9mail/src/main/res/layout/message_compose_recipients.xml b/k9mail/src/main/res/layout/message_compose_recipients.xml index c337751c6..09a9297d9 100644 --- a/k9mail/src/main/res/layout/message_compose_recipients.xml +++ b/k9mail/src/main/res/layout/message_compose_recipients.xml @@ -62,6 +62,14 @@ android:tint="@color/openpgp_grey" /> + + diff --git a/k9mail/src/main/res/values/strings.xml b/k9mail/src/main/res/values/strings.xml index 61db197c0..9eabbd0f2 100644 --- a/k9mail/src/main/res/values/strings.xml +++ b/k9mail/src/main/res/values/strings.xml @@ -1133,11 +1133,15 @@ Please submit bug reports, contribute new features and ask questions at Mobile Search in message bodies is disabled in this development version! No Drafts folder configured for this account! - Signing requested, but no key selected! - Private mode enabled, but cannot encrypt to all recipients! + Draft NOT saved without encryption! + Error with OpenPGP provider. Check your settings! + No key configured for signing! Please check your settings. + Private mode is enabled, but some recipients do not have keys! Never Sign or Encrypt. Always Sign, Never Encrypt. Always Sign, Encrypt if possible. Always Sign, Always Encrypt. + Cannot connect to crypto provider, check your settings or click crypto icon to retry! + Crypto provider access denied, click crypto icon to retry! diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java index 7d411c6e1..068e4fd57 100644 --- a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java @@ -57,6 +57,8 @@ public class OpenPgpApi { * PendingIntent RESULT_INTENT (if RESULT_CODE == RESULT_CODE_USER_INTERACTION_REQUIRED) */ + public static final String ACTION_CHECK_PERMISSION = "org.openintents.openpgp.action.CHECK_PERMISSION"; + /** * DEPRECATED * Same as ACTION_CLEARTEXT_SIGN @@ -203,7 +205,6 @@ public class OpenPgpApi { */ public static final String ACTION_GET_KEY = "org.openintents.openpgp.action.GET_KEY"; - /* Intent extras */ public static final String EXTRA_API_VERSION = "api_version"; @@ -418,4 +419,18 @@ public class OpenPgpApi { } } + + public interface PermissionPingCallback { + void onPgpPermissionCheckResult(Intent result); + } + + public void checkPermissionPing(final PermissionPingCallback permissionPingCallback) { + Intent intent = new Intent(OpenPgpApi.ACTION_CHECK_PERMISSION); + executeApiAsync(intent, null, null, new IOpenPgpCallback() { + @Override + public void onReturn(Intent result) { + permissionPingCallback.onPgpPermissionCheckResult(result); + } + }); + } }