Merge pull request #1513 from fiaxh/pgp_background_decryption

Daniel Gultsch created

PGP messages background decryption

Change summary

src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java    | 162 
src/main/java/eu/siacs/conversations/crypto/PgpEngine.java               |  19 
src/main/java/eu/siacs/conversations/entities/Account.java               |   7 
src/main/java/eu/siacs/conversations/entities/Conversation.java          |   1 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |  10 
src/main/java/eu/siacs/conversations/services/NotificationService.java   |   2 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  15 
src/main/java/eu/siacs/conversations/ui/ConversationActivity.java        |   6 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        | 138 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java      |   6 
src/main/res/values/strings.xml                                          |   3 
11 files changed, 294 insertions(+), 75 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java πŸ”—

@@ -0,0 +1,162 @@
+package eu.siacs.conversations.crypto;
+
+import android.app.PendingIntent;
+
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.UiCallback;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PgpDecryptionService {
+
+	private final XmppConnectionService xmppConnectionService;
+	private final ConcurrentHashMap<String, List<Message>> messages = new ConcurrentHashMap<>();
+	private final ConcurrentHashMap<String, Boolean> decryptingMessages = new ConcurrentHashMap<>();
+	private Boolean keychainLocked = false;
+	private final Object keychainLockedLock = new Object();
+
+	public PgpDecryptionService(XmppConnectionService xmppConnectionService) {
+		this.xmppConnectionService = xmppConnectionService;
+	}
+
+	public void add(Message message) {
+		if (isRunning()) {
+			decryptDirectly(message);
+		} else {
+			store(message);
+		}
+	}
+
+	public void addAll(List<Message> messagesList) {
+		if (!messagesList.isEmpty()) {
+			String conversationUuid = messagesList.get(0).getConversation().getUuid();
+			if (!messages.containsKey(conversationUuid)) {
+				List<Message> list = Collections.synchronizedList(new LinkedList<Message>());
+				messages.put(conversationUuid, list);
+			}
+			synchronized (messages.get(conversationUuid)) {
+				messages.get(conversationUuid).addAll(messagesList);
+			}
+			decryptAllMessages();
+		}
+	}
+
+	public void onKeychainUnlocked() {
+		synchronized (keychainLockedLock) {
+			keychainLocked = false;
+		}
+		decryptAllMessages();
+	}
+
+	public void onKeychainLocked() {
+		synchronized (keychainLockedLock) {
+			keychainLocked = true;
+		}
+		xmppConnectionService.updateConversationUi();
+	}
+
+	public void onOpenPgpServiceBound() {
+		decryptAllMessages();
+	}
+
+	public boolean isRunning() {
+		synchronized (keychainLockedLock) {
+			return !keychainLocked;
+		}
+	}
+
+	private void store(Message message) {
+		if (messages.containsKey(message.getConversation().getUuid())) {
+			messages.get(message.getConversation().getUuid()).add(message);
+		} else {
+			List<Message> messageList = Collections.synchronizedList(new LinkedList<Message>());
+			messageList.add(message);
+			messages.put(message.getConversation().getUuid(), messageList);
+		}
+	}
+
+	private void decryptAllMessages() {
+		for (String uuid : messages.keySet()) {
+			decryptMessages(uuid);
+		}
+	}
+
+	private void decryptMessages(final String uuid) {
+		synchronized (decryptingMessages) {
+			Boolean decrypting = decryptingMessages.get(uuid);
+			if ((decrypting != null && !decrypting) || decrypting == null) {
+				decryptingMessages.put(uuid, true);
+				decryptMessage(uuid);
+			}
+		}
+	}
+
+	private void decryptMessage(final String uuid) {
+		Message message = null;
+		synchronized (messages.get(uuid)) {
+			while (!messages.get(uuid).isEmpty()) {
+				if (messages.get(uuid).get(0).getEncryption() == Message.ENCRYPTION_PGP) {
+					if (isRunning()) {
+						message = messages.get(uuid).remove(0);
+					}
+					break;
+				} else {
+					messages.get(uuid).remove(0);
+				}
+			}
+			if (message != null && xmppConnectionService.getPgpEngine() != null) {
+				xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() {
+
+					@Override
+					public void userInputRequried(PendingIntent pi, Message message) {
+						messages.get(uuid).add(0, message);
+						decryptingMessages.put(uuid, false);
+					}
+
+					@Override
+					public void success(Message message) {
+						xmppConnectionService.updateConversationUi();
+						decryptMessage(uuid);
+					}
+
+					@Override
+					public void error(int error, Message message) {
+						message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+						xmppConnectionService.updateConversationUi();
+						decryptMessage(uuid);
+					}
+				});
+			} else {
+				decryptingMessages.put(uuid, false);
+			}
+		}
+	}
+
+	private void decryptDirectly(final Message message) {
+		if (message.getEncryption() == Message.ENCRYPTION_PGP && xmppConnectionService.getPgpEngine() != null) {
+			xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() {
+
+				@Override
+				public void userInputRequried(PendingIntent pi, Message message) {
+					store(message);
+				}
+
+				@Override
+				public void success(Message message) {
+					xmppConnectionService.updateConversationUi();
+					xmppConnectionService.getNotificationService().updateNotification(false);
+				}
+
+				@Override
+				public void error(int error, Message message) {
+					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+					xmppConnectionService.updateConversationUi();
+				}
+			});
+		}
+	}
+}

src/main/java/eu/siacs/conversations/crypto/PgpEngine.java πŸ”—

@@ -50,6 +50,7 @@ public class PgpEngine {
 
 				@Override
 				public void onReturn(Intent result) {
+					notifyPgpDecryptionService(message.getContact().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
 					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 							OpenPgpApi.RESULT_CODE_ERROR)) {
 					case OpenPgpApi.RESULT_CODE_SUCCESS:
@@ -64,6 +65,7 @@ public class PgpEngine {
 										&& manager.getAutoAcceptFileSize() > 0) {
 									manager.createNewDownloadConnection(message);
 								}
+								mXmppConnectionService.updateMessage(message);
 								callback.success(message);
 							}
 						} catch (IOException e) {
@@ -158,6 +160,7 @@ public class PgpEngine {
 
 				@Override
 				public void onReturn(Intent result) {
+					notifyPgpDecryptionService(message.getContact().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
 					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 							OpenPgpApi.RESULT_CODE_ERROR)) {
 					case OpenPgpApi.RESULT_CODE_SUCCESS:
@@ -203,6 +206,7 @@ public class PgpEngine {
 
 					@Override
 					public void onReturn(Intent result) {
+						notifyPgpDecryptionService(message.getContact().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
 						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 								OpenPgpApi.RESULT_CODE_ERROR)) {
 						case OpenPgpApi.RESULT_CODE_SUCCESS:
@@ -252,6 +256,7 @@ public class PgpEngine {
 		InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
 		ByteArrayOutputStream os = new ByteArrayOutputStream();
 		Intent result = api.executeApi(params, is, os);
+		notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
 		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 				OpenPgpApi.RESULT_CODE_ERROR)) {
 		case OpenPgpApi.RESULT_CODE_SUCCESS:
@@ -282,6 +287,7 @@ public class PgpEngine {
 
 			@Override
 			public void onReturn(Intent result) {
+				notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result);
 				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
 				case OpenPgpApi.RESULT_CODE_SUCCESS:
 					StringBuilder signatureBuilder = new StringBuilder();
@@ -368,4 +374,17 @@ public class PgpEngine {
 		return (PendingIntent) result
 				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
 	}
+
+	private void notifyPgpDecryptionService(Account account, String action, final Intent result) {
+		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+			case OpenPgpApi.RESULT_CODE_SUCCESS:
+				if (OpenPgpApi.ACTION_SIGN.equals(action)) {
+					account.getPgpDecryptionService().onKeychainUnlocked();
+				}
+				break;
+			case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+				account.getPgpDecryptionService().onKeychainLocked();
+				break;
+		}
+	}
 }

src/main/java/eu/siacs/conversations/entities/Account.java πŸ”—

@@ -4,6 +4,7 @@ import android.content.ContentValues;
 import android.database.Cursor;
 import android.os.SystemClock;
 
+import eu.siacs.conversations.crypto.PgpDecryptionService;
 import net.java.otr4j.crypto.OtrCryptoEngineImpl;
 import net.java.otr4j.crypto.OtrCryptoException;
 
@@ -137,6 +138,7 @@ public class Account extends AbstractEntity {
 	protected boolean online = false;
 	private OtrService mOtrService = null;
 	private AxolotlService axolotlService = null;
+	private PgpDecryptionService pgpDecryptionService = null;
 	private XmppConnection xmppConnection = null;
 	private long mEndGracePeriod = 0L;
 	private String otrFingerprint;
@@ -313,12 +315,17 @@ public class Account extends AbstractEntity {
 		if (xmppConnection != null) {
 			xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
 		}
+		this.pgpDecryptionService = new PgpDecryptionService(context);
 	}
 
 	public OtrService getOtrService() {
 		return this.mOtrService;
 	}
 
+	public PgpDecryptionService getPgpDecryptionService() {
+		return pgpDecryptionService;
+	}
+
 	public XmppConnection getXmppConnection() {
 		return this.xmppConnection;
 	}

src/main/java/eu/siacs/conversations/parser/MessageParser.java πŸ”—

@@ -3,6 +3,7 @@ package eu.siacs.conversations.parser;
 import android.util.Log;
 import android.util.Pair;
 
+import eu.siacs.conversations.crypto.PgpDecryptionService;
 import net.java.otr4j.session.Session;
 import net.java.otr4j.session.SessionStatus;
 
@@ -114,6 +115,13 @@ public class MessageParser extends AbstractParser implements
 		return finishedMessage;
 	}
 
+	private Message parsePGPChat(final Conversation conversation, String pgpEncrypted, int status) {
+		final Message message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
+		PgpDecryptionService pgpDecryptionService = conversation.getAccount().getPgpDecryptionService();
+		pgpDecryptionService.add(message);
+		return message;
+	}
+
 	private class Invite {
 		Jid jid;
 		String password;
@@ -337,7 +345,7 @@ public class MessageParser extends AbstractParser implements
 					message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
 				}
 			} else if (pgpEncrypted != null) {
-				message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
+				message = parsePGPChat(conversation, pgpEncrypted, status);
 			} else if (axolotlEncrypted != null) {
 				message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status);
 				if (message == null) {

src/main/java/eu/siacs/conversations/services/NotificationService.java πŸ”—

@@ -177,7 +177,7 @@ public class NotificationService {
 		mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary));
 	}
 
-	private void updateNotification(final boolean notify) {
+	public void updateNotification(final boolean notify) {
 		final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
 			.getSystemService(Context.NOTIFICATION_SERVICE);
 		final SharedPreferences preferences = mXmppConnectionService.getPreferences();

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java πŸ”—

@@ -37,6 +37,7 @@ import net.java.otr4j.session.SessionID;
 import net.java.otr4j.session.SessionImpl;
 import net.java.otr4j.session.SessionStatus;
 
+import org.openintents.openpgp.IOpenPgpService;
 import org.openintents.openpgp.util.OpenPgpApi;
 import org.openintents.openpgp.util.OpenPgpServiceConnection;
 
@@ -659,7 +660,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
 		this.fileObserver.startWatching();
 
-		this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain");
+		this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() {
+			@Override
+			public void onBound(IOpenPgpService service) {
+				for (Account account : accounts) {
+					if (account.getPgpDecryptionService() != null) {
+						account.getPgpDecryptionService().onOpenPgpServiceBound();
+					}
+				}
+			}
+
+			@Override
+			public void onError(Exception e) { }
+		});
 		this.pgpServiceConnection.bindToService();
 
 		this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);

src/main/java/eu/siacs/conversations/ui/ConversationActivity.java πŸ”—

@@ -1194,8 +1194,7 @@ public class ConversationActivity extends XmppActivity
 		super.onActivityResult(requestCode, resultCode, data);
 		if (resultCode == RESULT_OK) {
 			if (requestCode == REQUEST_DECRYPT_PGP) {
-				mConversationFragment.hideSnackbar();
-				mConversationFragment.updateMessages();
+				mConversationFragment.onActivityResult(requestCode, resultCode, data);
 			} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
 				mPendingImageUris.clear();
 				mPendingImageUris.addAll(extractUriFromIntent(data));
@@ -1240,6 +1239,9 @@ public class ConversationActivity extends XmppActivity
 		} else {
 			mPendingImageUris.clear();
 			mPendingFileUris.clear();
+			if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
+				mConversationFragment.onActivityResult(requestCode, resultCode, data);
+			}
 		}
 	}
 

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java πŸ”—

@@ -11,6 +11,7 @@ import android.content.Intent;
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
 import android.text.InputType;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -199,21 +200,47 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			}
 		}
 	};
-	private IntentSender askForPassphraseIntent = null;
+	private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0;
+	private final int KEYCHAIN_UNLOCK_REQUIRED = 1;
+	private final int KEYCHAIN_UNLOCK_PENDING = 2;
+	private int keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
 	protected OnClickListener clickToDecryptListener = new OnClickListener() {
 
 		@Override
 		public void onClick(View v) {
-			if (activity.hasPgp() && askForPassphraseIntent != null) {
-				try {
-					getActivity().startIntentSenderForResult(
-							askForPassphraseIntent,
-							ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
-							0, 0);
-					askForPassphraseIntent = null;
-				} catch (SendIntentException e) {
-					//
+			if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED
+					&& activity.hasPgp() && !conversation.getAccount().getPgpDecryptionService().isRunning()) {
+				keychainUnlock = KEYCHAIN_UNLOCK_PENDING;
+				updateSnackBar(conversation);
+				Message message = getLastPgpDecryptableMessage();
+				if (message != null) {
+					activity.xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() {
+						@Override
+						public void success(Message object) {
+							conversation.getAccount().getPgpDecryptionService().onKeychainUnlocked();
+							keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+						}
+
+						@Override
+						public void error(int errorCode, Message object) {
+							keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+						}
+
+						@Override
+						public void userInputRequried(PendingIntent pi, Message object) {
+							try {
+								activity.startIntentSenderForResult(pi.getIntentSender(),
+										ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0);
+							} catch (SendIntentException e) {
+								keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+								updatePgpMessages();
+							}
+						}
+					});
 				}
+			} else {
+				keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+				updatePgpMessages();
 			}
 		}
 	};
@@ -224,8 +251,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			activity.verifyOtrSessionDialog(conversation, v);
 		}
 	};
-	private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>();
-	private boolean mDecryptJobRunning = false;
 	private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
 
 		@Override
@@ -629,7 +654,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 
 	@Override
 	public void onStop() {
-		mDecryptJobRunning = false;
 		super.onStop();
 		if (this.conversation != null) {
 			final String msg = mEditMessage.getText().toString();
@@ -661,10 +685,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			this.conversation.trim();
 		}
 
-		this.askForPassphraseIntent = null;
+		this.keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
 		this.conversation = conversation;
-		this.mDecryptJobRunning = false;
-		this.mEncryptedMessages.clear();
 		if (this.conversation.getMode() == Conversation.MODE_MULTI) {
 			this.conversation.setNextCounterpart(null);
 		}
@@ -767,7 +789,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 				default:
 					break;
 			}
-		} else if (askForPassphraseIntent != null) {
+		} else if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED) {
 			showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener);
 		} else if (mode == Conversation.MODE_SINGLE
 				&& conversation.smpRequested()) {
@@ -791,19 +813,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			}
 			final ConversationActivity activity = (ConversationActivity) getActivity();
 			if (this.conversation != null) {
-				updateSnackBar(this.conversation);
 				conversation.populateWithMessages(ConversationFragment.this.messageList);
-				for (final Message message : this.messageList) {
-					if (message.getEncryption() == Message.ENCRYPTION_PGP
-							&& (message.getStatus() == Message.STATUS_RECEIVED || message
-							.getStatus() >= Message.STATUS_SEND)
-							&& message.getTransferable() == null) {
-						if (!mEncryptedMessages.contains(message)) {
-							mEncryptedMessages.add(message);
-						}
-					}
-				}
-				decryptNext();
+				updatePgpMessages();
+				updateSnackBar(conversation);
 				updateStatusMessages();
 				this.messageListAdapter.notifyDataSetChanged();
 				updateChatMsgHint();
@@ -815,46 +827,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 		}
 	}
 
-	private void decryptNext() {
-		Message next = this.mEncryptedMessages.peek();
-		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
-
-		if (next != null && engine != null && !mDecryptJobRunning) {
-			mDecryptJobRunning = true;
-			engine.decrypt(next, new UiCallback<Message>() {
-
-				@Override
-				public void userInputRequried(PendingIntent pi, Message message) {
-					mDecryptJobRunning = false;
-					askForPassphraseIntent = pi.getIntentSender();
-					updateSnackBar(conversation);
-				}
-
-				@Override
-				public void success(Message message) {
-					mDecryptJobRunning = false;
-					try {
-						mEncryptedMessages.remove();
-					} catch (final NoSuchElementException ignored) {
-
-					}
-					askForPassphraseIntent = null;
-					activity.xmppConnectionService.updateMessage(message);
-				}
-
-				@Override
-				public void error(int error, Message message) {
-					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
-					mDecryptJobRunning = false;
-					try {
-						mEncryptedMessages.remove();
-					} catch (final NoSuchElementException ignored) {
+	public void updatePgpMessages() {
+		if (keychainUnlock != KEYCHAIN_UNLOCK_PENDING) {
+			if (getLastPgpDecryptableMessage() != null
+					&& !conversation.getAccount().getPgpDecryptionService().isRunning()) {
+				keychainUnlock = KEYCHAIN_UNLOCK_REQUIRED;
+			} else {
+				keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+			}
+		}
+	}
 
-					}
-					activity.refreshUi();
-				}
-			});
+	@Nullable
+	private Message getLastPgpDecryptableMessage() {
+		for (final Message message : this.messageList) {
+			if (message.getEncryption() == Message.ENCRYPTION_PGP
+					&& (message.getStatus() == Message.STATUS_RECEIVED || message.getStatus() >= Message.STATUS_SEND)
+					&& message.getTransferable() == null) {
+				return message;
+			}
 		}
+		return null;
 	}
 
 	private void messageSent() {
@@ -1274,7 +1267,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 	public void onActivityResult(int requestCode, int resultCode,
 	                                final Intent data) {
 		if (resultCode == Activity.RESULT_OK) {
-			if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
+			if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
+				activity.getSelectedConversation().getAccount().getPgpDecryptionService().onKeychainUnlocked();
+				keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+				updatePgpMessages();
+			} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
 				final String body = mEditMessage.getText().toString();
 				Message message = new Message(conversation, body, conversation.getNextEncryption());
 				sendAxolotlMessage(message);
@@ -1282,6 +1279,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 				int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
 				activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption());
 			}
+		} else {
+			if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
+				keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
+				updatePgpMessages();
+			}
 		}
 	}
 

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java πŸ”—

@@ -550,7 +550,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 			}
 		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 			if (activity.hasPgp()) {
-				displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message),darkBackground);
+				if (account.getPgpDecryptionService().isRunning()) {
+					displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground);
+				} else {
+					displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground);
+				}
 			} else {
 				displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground);
 				if (viewHolder != null) {

src/main/res/values/strings.xml πŸ”—

@@ -30,7 +30,8 @@
 	<string name="minutes_ago">%d mins ago</string>
 	<string name="unread_conversations">unread Conversations</string>
 	<string name="sending">sending…</string>
-	<string name="encrypted_message">Decrypting message. Please wait…</string>
+	<string name="message_decrypting">Decrypting message. Please wait…</string>
+	<string name="pgp_message">OpenPGP encrypted message</string>
 	<string name="nick_in_use">Nickname is already in use</string>
 	<string name="admin">Admin</string>
 	<string name="owner">Owner</string>