refactored pgp decryption

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java    | 305 
src/main/java/eu/siacs/conversations/crypto/PgpEngine.java               | 107 
src/main/java/eu/siacs/conversations/entities/Account.java               |  14 
src/main/java/eu/siacs/conversations/entities/Conversation.java          |   2 
src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java    |   2 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |   6 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  17 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        |  91 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java      |   4 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java   |   2 
src/main/res/values/strings.xml                                          |   1 
11 files changed, 211 insertions(+), 340 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java 🔗

@@ -1,162 +1,181 @@
 package eu.siacs.conversations.crypto;
 
 import android.app.PendingIntent;
+import android.content.Intent;
+
+import org.openintents.openpgp.util.OpenPgpApi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayDeque;
+import java.util.List;
 
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.http.HttpConnectionManager;
 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();
-	}
+    private final XmppConnectionService mXmppConnectionService;
+    private OpenPgpApi openPgpApi = null;
 
-	public void onOpenPgpServiceBound() {
-		decryptAllMessages();
-	}
-
-	public boolean isRunning() {
-		synchronized (keychainLockedLock) {
-			return !keychainLocked;
-		}
-	}
+	protected final ArrayDeque<Message> messages = new ArrayDeque();
+	Message currentMessage;
+    private PendingIntent pendingIntent;
 
-	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);
-		}
-	}
+    public PgpDecryptionService(XmppConnectionService service) {
+        this.mXmppConnectionService = service;
+    }
 
-	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);
-			}
-		}
+	public synchronized void decrypt(final Message message) {
+        messages.add(message);
+		continueDecryption();
 	}
 
-	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);
-			}
+    public synchronized void decrypt(final List<Message> list) {
+        for(Message message : list) {
+            if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+                messages.add(message);
+            }
+        }
+        continueDecryption();
+    }
+
+	protected synchronized void decryptNext() {
+		if (pendingIntent == null
+                && getOpenPgpApi() != null
+                && (currentMessage =  messages.poll()) != null) {
+			new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    executeApi(currentMessage);
+                    decryptNext();
+                }
+            }).start();
 		}
 	}
 
-	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();
-				}
-			});
-		}
-	}
+    public synchronized void continueDecryption(boolean resetPending) {
+        if (resetPending) {
+            this.pendingIntent = null;
+        }
+        continueDecryption();
+    }
+
+    public synchronized void continueDecryption() {
+        if (currentMessage == null) {
+            decryptNext();
+        }
+    }
+
+    private synchronized OpenPgpApi getOpenPgpApi() {
+        if (openPgpApi == null) {
+            this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
+        }
+        return this.openPgpApi;
+    }
+
+    private void executeApi(Message message) {
+        Intent params = new Intent();
+        params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
+        if (message.getType() == Message.TYPE_TEXT) {
+            InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
+            final OutputStream os = new ByteArrayOutputStream();
+            Intent result = getOpenPgpApi().executeApi(params, is, os);
+            switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+                case OpenPgpApi.RESULT_CODE_SUCCESS:
+                    try {
+                        os.flush();
+                        message.setBody(os.toString());
+                        message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+                        final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
+                        if (message.trusted()
+                                && message.treatAsDownloadable() != Message.Decision.NEVER
+                                && manager.getAutoAcceptFileSize() > 0) {
+                            manager.createNewDownloadConnection(message);
+                        }
+                    } catch (IOException e) {
+                        message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+                    }
+                    mXmppConnectionService.updateMessage(message);
+                    break;
+                case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                    messages.addFirst(message);
+                    currentMessage = null;
+                    storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+                    break;
+                case OpenPgpApi.RESULT_CODE_ERROR:
+                    message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+                    mXmppConnectionService.updateMessage(message);
+                    break;
+            }
+        } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
+            try {
+                final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
+                final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
+                outputFile.getParentFile().mkdirs();
+                outputFile.createNewFile();
+                InputStream is = new FileInputStream(inputFile);
+                OutputStream os = new FileOutputStream(outputFile);
+                Intent result = getOpenPgpApi().executeApi(params, is, os);
+                switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+                    case OpenPgpApi.RESULT_CODE_SUCCESS:
+                        URL url = message.getFileParams().url;
+                        mXmppConnectionService.getFileBackend().updateFileParams(message,url);
+                        message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+                        inputFile.delete();
+                        mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
+                        mXmppConnectionService.updateMessage(message);
+                        break;
+                    case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                        messages.addFirst(message);
+                        currentMessage = null;
+                        storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+                        break;
+                    case OpenPgpApi.RESULT_CODE_ERROR:
+                        message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+                        mXmppConnectionService.updateMessage(message);
+                        break;
+                }
+            } catch (final IOException e) {
+                message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
+                mXmppConnectionService.updateMessage(message);
+            }
+        }
+    }
+
+    private void storePendingIntent(PendingIntent pendingIntent) {
+        this.pendingIntent = pendingIntent;
+        mXmppConnectionService.updateConversationUi();
+    }
+
+    public synchronized boolean hasPendingIntent(Conversation conversation) {
+        if (pendingIntent == null) {
+            return false;
+        } else {
+            for(Message message : messages) {
+                if (message.getConversation() == conversation) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public PendingIntent getPendingIntent() {
+        return pendingIntent;
+    }
+
+    public boolean isConnected() {
+        return getOpenPgpApi() != null;
+    }
 }

src/main/java/eu/siacs/conversations/crypto/PgpEngine.java 🔗

@@ -38,96 +38,6 @@ public class PgpEngine {
 		this.mXmppConnectionService = service;
 	}
 
-	public void decrypt(final Message message, final UiCallback<Message> callback) {
-		Intent params = new Intent();
-		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
-		final String uuid = message.getUuid();
-		if (message.getType() == Message.TYPE_TEXT) {
-			InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
-			final OutputStream os = new ByteArrayOutputStream();
-			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-
-				@Override
-				public void onReturn(Intent result) {
-					notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
-					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
-					case OpenPgpApi.RESULT_CODE_SUCCESS:
-						try {
-							os.flush();
-							if (message.getEncryption() == Message.ENCRYPTION_PGP
-									&& message.getUuid().equals(uuid)) {
-								message.setBody(os.toString());
-								message.setEncryption(Message.ENCRYPTION_DECRYPTED);
-								final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
-								if (message.trusted()
-										&& message.treatAsDownloadable() != Message.Decision.NEVER
-										&& manager.getAutoAcceptFileSize() > 0) {
-									manager.createNewDownloadConnection(message);
-								}
-								mXmppConnectionService.updateMessage(message);
-								callback.success(message);
-							}
-						} catch (IOException e) {
-							callback.error(R.string.openpgp_error, message);
-							return;
-						}
-
-						return;
-					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-						callback.userInputRequried((PendingIntent) result
-								.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
-								message);
-						return;
-					case OpenPgpApi.RESULT_CODE_ERROR:
-						callback.error(R.string.openpgp_error, message);
-                    }
-				}
-			});
-		} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
-			try {
-				final DownloadableFile inputFile = this.mXmppConnectionService
-						.getFileBackend().getFile(message, false);
-				final DownloadableFile outputFile = this.mXmppConnectionService
-						.getFileBackend().getFile(message, true);
-				outputFile.getParentFile().mkdirs();
-				outputFile.createNewFile();
-				InputStream is = new FileInputStream(inputFile);
-				OutputStream os = new FileOutputStream(outputFile);
-				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-
-					@Override
-					public void onReturn(Intent result) {
-						notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
-						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
-								OpenPgpApi.RESULT_CODE_ERROR)) {
-						case OpenPgpApi.RESULT_CODE_SUCCESS:
-							URL url = message.getFileParams().url;
-							mXmppConnectionService.getFileBackend().updateFileParams(message,url);
-							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
-							PgpEngine.this.mXmppConnectionService
-									.updateMessage(message);
-							inputFile.delete();
-							mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
-							callback.success(message);
-							return;
-						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-							callback.userInputRequried(
-									(PendingIntent) result
-											.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
-									message);
-							return;
-						case OpenPgpApi.RESULT_CODE_ERROR:
-							callback.error(R.string.openpgp_error, message);
-						}
-					}
-				});
-			} catch (final IOException e) {
-				callback.error(R.string.error_decrypting_file, message);
-			}
-
-		}
-	}
-
 	public void encrypt(final Message message, final UiCallback<Message> callback) {
 		Intent params = new Intent();
 		params.setAction(OpenPgpApi.ACTION_ENCRYPT);
@@ -156,7 +66,6 @@ public class PgpEngine {
 
 				@Override
 				public void onReturn(Intent result) {
-					notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
 					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 							OpenPgpApi.RESULT_CODE_ERROR)) {
 					case OpenPgpApi.RESULT_CODE_SUCCESS:
@@ -202,7 +111,6 @@ public class PgpEngine {
 
 					@Override
 					public void onReturn(Intent result) {
-						notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
 						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 								OpenPgpApi.RESULT_CODE_ERROR)) {
 						case OpenPgpApi.RESULT_CODE_SUCCESS:
@@ -257,7 +165,6 @@ 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:
@@ -315,7 +222,6 @@ 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();
@@ -397,17 +303,4 @@ 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 🔗

@@ -6,6 +6,7 @@ import android.os.SystemClock;
 import android.util.Pair;
 
 import eu.siacs.conversations.crypto.PgpDecryptionService;
+
 import net.java.otr4j.crypto.OtrCryptoEngineImpl;
 import net.java.otr4j.crypto.OtrCryptoException;
 
@@ -20,7 +21,6 @@ import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 
-import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.OtrService;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -83,6 +83,14 @@ public class Account extends AbstractEntity {
 		return getRoster().getContact(jid);
 	}
 
+	public boolean hasPendingPgpIntent(Conversation conversation) {
+		return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation);
+	}
+
+	public boolean isPgpDecryptionServiceConnected() {
+		return pgpDecryptionService != null && pgpDecryptionService.isConnected();
+	}
+
 	public enum State {
 		DISABLED,
 		OFFLINE,
@@ -398,10 +406,10 @@ public class Account extends AbstractEntity {
 	public void initAccountServices(final XmppConnectionService context) {
 		this.mOtrService = new OtrService(context, this);
 		this.axolotlService = new AxolotlService(this, context);
+		this.pgpDecryptionService = new PgpDecryptionService(context);
 		if (xmppConnection != null) {
 			xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
 		}
-		this.pgpDecryptionService = new PgpDecryptionService(context);
 	}
 
 	public OtrService getOtrService() {
@@ -409,7 +417,7 @@ public class Account extends AbstractEntity {
 	}
 
 	public PgpDecryptionService getPgpDecryptionService() {
-		return pgpDecryptionService;
+		return this.pgpDecryptionService;
 	}
 
 	public XmppConnection getXmppConnection() {

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -932,7 +932,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
 		synchronized (this.messages) {
 			this.messages.addAll(index, messages);
 		}
-		account.getPgpDecryptionService().addAll(messages);
+		account.getPgpDecryptionService().decrypt(messages);
 	}
 
 	public void sort() {

src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java 🔗

@@ -127,7 +127,7 @@ public class HttpDownloadConnection implements Transferable {
 		message.setTransferable(null);
 		mHttpConnectionManager.finishConnection(this);
 		if (message.getEncryption() == Message.ENCRYPTION_PGP) {
-			message.getConversation().getAccount().getPgpDecryptionService().add(message);
+			message.getConversation().getAccount().getPgpDecryptionService().decrypt(message);
 		}
 		mXmppConnectionService.updateConversationUi();
 		if (acceptedAutomatically) {

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -7,7 +7,6 @@ import android.util.Pair;
 import net.java.otr4j.session.Session;
 import net.java.otr4j.session.SessionStatus;
 
-import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -32,7 +31,6 @@ import eu.siacs.conversations.http.HttpConnectionManager;
 import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.Xmlns;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
@@ -484,7 +482,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 							sendMessageReceipts(account, packet);
 						}
 						if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
-							conversation.getAccount().getPgpDecryptionService().add(replacedMessage);
+							conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage);
 						}
 						return;
 					} else {
@@ -508,7 +506,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 			}
 
 			if (message.getEncryption() == Message.ENCRYPTION_PGP) {
-				conversation.getAccount().getPgpDecryptionService().add(message);
+				conversation.getAccount().getPgpDecryptionService().decrypt(message);
 			}
 
 			if (query == null || query.getWith() == null) { //either no mam or catchup

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -49,7 +49,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -62,6 +61,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import de.duenndns.ssl.MemorizingTrustManager;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.PgpDecryptionService;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
@@ -383,6 +383,16 @@ public class XmppConnectionService extends Service {
 
 	}
 
+	public OpenPgpApi getOpenPgpApi() {
+		if (!Config.supportOpenPgp()) {
+			return null;
+		} else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) {
+			return new OpenPgpApi(this, pgpServiceConnection.getService());
+		} else {
+			return null;
+		}
+	}
+
 	public FileBackend getFileBackend() {
 		return this.fileBackend;
 	}
@@ -754,8 +764,9 @@ public class XmppConnectionService extends Service {
 				@Override
 				public void onBound(IOpenPgpService2 service) {
 					for (Account account : accounts) {
-						if (account.getPgpDecryptionService() != null) {
-							account.getPgpDecryptionService().onOpenPgpServiceBound();
+						final PgpDecryptionService pgp = account.getPgpDecryptionService();
+						if(pgp != null) {
+							pgp.continueDecryption(true);
 						}
 					}
 				}

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -9,12 +9,9 @@ import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentSender.SendIntentException;
-import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.os.Handler;
-import android.support.annotation.Nullable;
 import android.text.InputType;
-import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Gravity;
@@ -214,49 +211,25 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 		}
 		return -1;
 	}
-
-	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 (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();
-							}
-						}
-					});
+			PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent();
+			if (pendingIntent != null) {
+				try {
+					activity.startIntentSenderForResult(pendingIntent.getIntentSender(),
+                            ConversationActivity.REQUEST_DECRYPT_PGP,
+                            null,
+                            0,
+                            0,
+                            0);
+				} catch (SendIntentException e) {
+					Toast.makeText(activity,R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show();
+					conversation.getAccount().getPgpDecryptionService().continueDecryption(true);
 				}
-			} else {
-				keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
-				updatePgpMessages();
 			}
+			updateSnackBar(conversation);
 		}
 	};
 	protected OnClickListener clickToVerify = new OnClickListener() {
@@ -722,7 +695,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 		message.setEncryption(Message.ENCRYPTION_PGP);
 		activity.updateConversationList();
 		updateMessages();
-		conversation.getAccount().getPgpDecryptionService().add(message);
+		conversation.getAccount().getPgpDecryptionService().decrypt(message);
 	}
 
 	protected void privateMessageWith(final Jid counterpart) {
@@ -789,7 +762,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			this.conversation.trim();
 		}
 
-		this.keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
 		this.conversation = conversation;
 		boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating();
 		this.mEditMessage.setEnabled(canWrite);
@@ -909,7 +881,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 				default:
 					break;
 			}
-		} else if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED) {
+		} else if (account.hasPendingPgpIntent(conversation)) {
 			showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener);
 		} else if (mode == Conversation.MODE_SINGLE
 				&& conversation.smpRequested()) {
@@ -932,7 +904,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			final ConversationActivity activity = (ConversationActivity) getActivity();
 			if (this.conversation != null) {
 				conversation.populateWithMessages(ConversationFragment.this.messageList);
-				updatePgpMessages();
 				updateSnackBar(conversation);
 				updateStatusMessages();
 				this.messageListAdapter.notifyDataSetChanged();
@@ -945,29 +916,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 		}
 	}
 
-	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;
-			}
-		}
-	}
-
-	@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() {
 		mEditMessage.setText("");
 		updateChatMsgHint();
@@ -1424,9 +1372,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 	                                final Intent data) {
 		if (resultCode == Activity.RESULT_OK) {
 			if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
-				activity.getSelectedConversation().getAccount().getPgpDecryptionService().onKeychainUnlocked();
-				keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED;
-				updatePgpMessages();
+				activity.getSelectedConversation().getAccount().getPgpDecryptionService().continueDecryption(true);
 			} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
 				final String body = mEditMessage.getText().toString();
 				Message message = new Message(conversation, body, conversation.getNextEncryption());
@@ -1435,11 +1381,6 @@ 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 🔗

@@ -631,8 +631,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 				displayOpenableMessage(viewHolder, message);
 			}
 		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
-			if (activity.hasPgp()) {
-				if (account.getPgpDecryptionService().isRunning()) {
+			if (account.isPgpDecryptionServiceConnected()) {
+				if (!account.hasPendingPgpIntent(conversation)) {
 					displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground);
 				} else {
 					displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground);

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java 🔗

@@ -110,7 +110,7 @@ public class JingleConnection implements Transferable {
 			if (message.getEncryption() != Message.ENCRYPTION_PGP) {
 				mXmppConnectionService.getFileBackend().updateMediaScanner(file);
 			} else {
-				account.getPgpDecryptionService().add(message);
+				account.getPgpDecryptionService().decrypt(message);
 			}
 		}
 

src/main/res/values/strings.xml 🔗

@@ -662,4 +662,5 @@
 	<string name="pref_theme_dark">Dark theme</string>
 	<string name="pref_use_green_background">Green Background</string>
 	<string name="pref_use_green_background_summary">Use green background for received messages</string>
+	<string name="unable_to_connect_to_keychain">Unable to connect to OpenKeychain</string>
 </resources>