diff --git a/gen/de/gultsch/chat/R.java b/gen/de/gultsch/chat/R.java
index a1e79626e..3562366f1 100644
--- a/gen/de/gultsch/chat/R.java
+++ b/gen/de/gultsch/chat/R.java
@@ -9,9 +9,9 @@ package de.gultsch.chat;
public final class R {
public static final class array {
- public static final int conversation_encryption_type_entries=0x7f050000;
- public static final int conversation_encryption_type_values=0x7f050001;
- public static final int manage_account_options=0x7f050002;
+ public static final int conversation_encryption_type_entries=0x7f060000;
+ public static final int conversation_encryption_type_values=0x7f060001;
+ public static final int manage_account_options=0x7f060002;
}
public static final class attr {
}
@@ -22,24 +22,25 @@ public final class R {
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
*/
- public static final int activity_horizontal_margin=0x7f060000;
- public static final int activity_vertical_margin=0x7f060001;
+ public static final int activity_horizontal_margin=0x7f070000;
+ public static final int activity_vertical_margin=0x7f070001;
}
public static final class drawable {
public static final int es_slidingpane_shadow=0x7f020000;
public static final int ic_action_add=0x7f020001;
public static final int ic_action_add_person=0x7f020002;
- public static final int ic_action_delete=0x7f020003;
- public static final int ic_action_refresh=0x7f020004;
- public static final int ic_action_secure=0x7f020005;
- public static final int ic_action_send=0x7f020006;
- public static final int ic_action_send_now=0x7f020007;
- public static final int ic_action_unsecure=0x7f020008;
- public static final int ic_launcher=0x7f020009;
- public static final int ic_profile=0x7f02000a;
- public static final int message_border=0x7f02000b;
- public static final int notification=0x7f02000c;
- public static final int section_header=0x7f02000d;
+ public static final int ic_action_cancel_launchersize=0x7f020003;
+ public static final int ic_action_delete=0x7f020004;
+ public static final int ic_action_refresh=0x7f020005;
+ public static final int ic_action_secure=0x7f020006;
+ public static final int ic_action_send=0x7f020007;
+ public static final int ic_action_send_now=0x7f020008;
+ public static final int ic_action_unsecure=0x7f020009;
+ public static final int ic_launcher=0x7f02000a;
+ public static final int ic_profile=0x7f02000b;
+ public static final int message_border=0x7f02000c;
+ public static final int notification=0x7f02000d;
+ public static final int section_header=0x7f02000e;
}
public static final class id {
public static final int account_confirm_password_desc=0x7f0a001c;
@@ -57,9 +58,10 @@ public final class R {
public static final int action_add_account=0x7f0a0034;
public static final int action_archive=0x7f0a002e;
public static final int action_details=0x7f0a002d;
- public static final int action_refresh_contacts=0x7f0a0038;
+ public static final int action_refresh_contacts=0x7f0a0039;
public static final int action_security=0x7f0a002c;
public static final int action_settings=0x7f0a0030;
+ public static final int announce_pgp=0x7f0a0038;
public static final int contactList=0x7f0a0006;
public static final int contact_display_name=0x7f0a0008;
public static final int contact_jid=0x7f0a0009;
@@ -123,17 +125,21 @@ public final class R {
public static final int newconversation=0x7f090004;
}
public static final class string {
- public static final int action_accounts=0x7f070003;
- public static final int action_add=0x7f070002;
- public static final int action_add_account=0x7f070007;
- public static final int action_archive=0x7f070004;
- public static final int action_details=0x7f070005;
- public static final int action_secure=0x7f070006;
- public static final int action_settings=0x7f070001;
- public static final int app_name=0x7f070000;
- public static final int just_now=0x7f070009;
- public static final int sending=0x7f07000a;
- public static final int title_activity_new_conversation=0x7f070008;
+ public static final int action_accounts=0x7f050005;
+ public static final int action_add=0x7f050004;
+ public static final int action_add_account=0x7f050009;
+ public static final int action_archive=0x7f050006;
+ public static final int action_details=0x7f050007;
+ public static final int action_secure=0x7f050008;
+ public static final int action_settings=0x7f050003;
+ public static final int announce_pgp=0x7f05000d;
+ public static final int app_name=0x7f050002;
+ public static final int encrypted_message=0x7f05000e;
+ public static final int just_now=0x7f05000b;
+ public static final int openpgp_install_openkeychain_via=0x7f050001;
+ public static final int openpgp_list_preference_none=0x7f050000;
+ public static final int sending=0x7f05000c;
+ public static final int title_activity_new_conversation=0x7f05000a;
}
public static final class style {
/**
diff --git a/res/layout/fragment_conversation.xml b/res/layout/fragment_conversation.xml
index ad5f34b80..50b9ba3ba 100644
--- a/res/layout/fragment_conversation.xml
+++ b/res/layout/fragment_conversation.xml
@@ -84,5 +84,4 @@
android:typeface="monospace"/>
-
\ No newline at end of file
diff --git a/res/layout/message_recieved.xml b/res/layout/message_recieved.xml
index bde702aee..2ae830346 100644
--- a/res/layout/message_recieved.xml
+++ b/res/layout/message_recieved.xml
@@ -24,6 +24,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hi, how are you?"
+ android:autoLink="all"
android:textSize="16sp"
android:id="@+id/message_body"
android:textColor="#333333"/>
diff --git a/res/layout/message_sent.xml b/res/layout/message_sent.xml
index c3136a55e..b553ff622 100644
--- a/res/layout/message_sent.xml
+++ b/res/layout/message_sent.xml
@@ -26,6 +26,7 @@
android:text="Hi, how are you?"
android:textSize="16sp"
android:id="@+id/message_body"
+ android:autoLink="all"
android:textColor="#333333"/>
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a084e890e..a545676e2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12,4 +12,6 @@
New Conversation
just now
sending…
+ Renew PGP announcement
+ This message is encrypted. Click to decrypt.
diff --git a/src/de/gultsch/chat/crypto/OtrEngine.java b/src/de/gultsch/chat/crypto/OtrEngine.java
index 7be9bed31..cdd71d61b 100644
--- a/src/de/gultsch/chat/crypto/OtrEngine.java
+++ b/src/de/gultsch/chat/crypto/OtrEngine.java
@@ -93,8 +93,6 @@ public class OtrEngine implements OtrEngineHost {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
- } catch (JSONException e) {
- e.printStackTrace();
}
}
diff --git a/src/de/gultsch/chat/crypto/PgpEngine.java b/src/de/gultsch/chat/crypto/PgpEngine.java
new file mode 100644
index 000000000..6d94e577f
--- /dev/null
+++ b/src/de/gultsch/chat/crypto/PgpEngine.java
@@ -0,0 +1,148 @@
+package de.gultsch.chat.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.openintents.openpgp.util.OpenPgpConstants;
+
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class PgpEngine {
+ private OpenPgpApi api;
+
+ public PgpEngine(OpenPgpApi api) {
+ this.api = api;
+ }
+
+ public String decrypt(String message) throws UserInputRequiredException,
+ OpenPgpException {
+ InputStream is = new ByteArrayInputStream(message.getBytes());
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bundle result = api.decryptAndVerify(is, os);
+ switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
+ case OpenPgpConstants.RESULT_CODE_SUCCESS:
+ return os.toString();
+ case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ throw new UserInputRequiredException(
+ (PendingIntent) result
+ .getParcelable(OpenPgpConstants.RESULT_INTENT));
+ case OpenPgpConstants.RESULT_CODE_ERROR:
+ throw new OpenPgpException(
+ (OpenPgpError) result
+ .getParcelable(OpenPgpConstants.RESULT_ERRORS));
+ default:
+ return null;
+ }
+ }
+
+ public String encrypt(long keyId, String message) {
+ Bundle params = new Bundle();
+ params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
+ long[] keyIds = { keyId };
+ params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, keyIds);
+
+ InputStream is = new ByteArrayInputStream(message.getBytes());
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bundle result = api.encrypt(params, is, os);
+ StringBuilder encryptedMessageBody = new StringBuilder();
+ String[] lines = os.toString().split("\n");
+ for (int i = 3; i < lines.length - 1; ++i) {
+ encryptedMessageBody.append(lines[i].trim());
+ }
+ return encryptedMessageBody.toString();
+ }
+
+ public long fetchKeyId(String status, String signature)
+ throws OpenPgpException {
+ StringBuilder pgpSig = new StringBuilder();
+ pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
+ pgpSig.append('\n');
+ pgpSig.append("Hash: SHA1");
+ pgpSig.append('\n');
+ pgpSig.append('\n');
+ pgpSig.append(status);
+ pgpSig.append('\n');
+ pgpSig.append("-----BEGIN PGP SIGNATURE-----");
+ pgpSig.append('\n');
+ pgpSig.append('\n');
+ pgpSig.append(signature.replace("\n", "").trim());
+ pgpSig.append('\n');
+ pgpSig.append("-----END PGP SIGNATURE-----");
+ Bundle params = new Bundle();
+ params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
+ InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bundle result = api.decryptAndVerify(params, is, os);
+ switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
+ case OpenPgpConstants.RESULT_CODE_SUCCESS:
+ OpenPgpSignatureResult sigResult = result
+ .getParcelable(OpenPgpConstants.RESULT_SIGNATURE);
+ return sigResult.getKeyId();
+ case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ break;
+ case OpenPgpConstants.RESULT_CODE_ERROR:
+ throw new OpenPgpException(
+ (OpenPgpError) result
+ .getParcelable(OpenPgpConstants.RESULT_ERRORS));
+ }
+ return 0;
+ }
+
+ public String generateSignature(String status)
+ throws UserInputRequiredException {
+ Bundle params = new Bundle();
+ params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
+ InputStream is = new ByteArrayInputStream(status.getBytes());
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bundle result = api.sign(params, is, os);
+ StringBuilder signatureBuilder = new StringBuilder();
+ switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
+ case OpenPgpConstants.RESULT_CODE_SUCCESS:
+ String[] lines = os.toString().split("\n");
+ for (int i = 7; i < lines.length - 1; ++i) {
+ signatureBuilder.append(lines[i].trim());
+ }
+ break;
+ case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ UserInputRequiredException exception = new UserInputRequiredException(
+ (PendingIntent) result
+ .getParcelable(OpenPgpConstants.RESULT_INTENT));
+ throw exception;
+ case OpenPgpConstants.RESULT_CODE_ERROR:
+ break;
+ }
+ return signatureBuilder.toString();
+ }
+
+ public class UserInputRequiredException extends Exception {
+ private static final long serialVersionUID = -6913480043269132016L;
+ private PendingIntent pi;
+
+ public UserInputRequiredException(PendingIntent pi) {
+ this.pi = pi;
+ }
+
+ public PendingIntent getPendingIntent() {
+ return this.pi;
+ }
+ }
+
+ public class OpenPgpException extends Exception {
+ private static final long serialVersionUID = -7324789703473056077L;
+ private OpenPgpError error;
+
+ public OpenPgpException(OpenPgpError openPgpError) {
+ this.error = openPgpError;
+ }
+
+ public OpenPgpError getOpenPgpError() {
+ return this.error;
+ }
+ }
+}
diff --git a/src/de/gultsch/chat/entities/Account.java b/src/de/gultsch/chat/entities/Account.java
index 3ffc75f92..c8f6d0639 100644
--- a/src/de/gultsch/chat/entities/Account.java
+++ b/src/de/gultsch/chat/entities/Account.java
@@ -137,8 +137,13 @@ public class Account extends AbstractEntity{
return keys;
}
- public void setKey(String keyName, String keyValue) throws JSONException {
- this.keys.put(keyName, keyValue);
+ public boolean setKey(String keyName, String keyValue) {
+ try {
+ this.keys.put(keyName, keyValue);
+ return true;
+ } catch (JSONException e) {
+ return false;
+ }
}
@Override
diff --git a/src/de/gultsch/chat/entities/Contact.java b/src/de/gultsch/chat/entities/Contact.java
index a0dbf22fe..a8c9fa621 100644
--- a/src/de/gultsch/chat/entities/Contact.java
+++ b/src/de/gultsch/chat/entities/Contact.java
@@ -221,6 +221,26 @@ public class Contact extends AbstractEntity implements Serializable {
}
}
+ public void setPgpKeyId(long keyId) {
+ try {
+ this.keys.put("pgp_keyid", keyId);
+ } catch (JSONException e) {
+
+ }
+ }
+
+ public long getPgpKeyId() {
+ if (this.keys.has("pgp_keyid")) {
+ try {
+ return this.keys.getLong("pgp_keyid");
+ } catch (JSONException e) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
public void setSubscriptionOption(int option) {
this.subscription |= 1 << option;
}
diff --git a/src/de/gultsch/chat/entities/Message.java b/src/de/gultsch/chat/entities/Message.java
index 9b308c754..58e4ef63a 100644
--- a/src/de/gultsch/chat/entities/Message.java
+++ b/src/de/gultsch/chat/entities/Message.java
@@ -17,6 +17,7 @@ public class Message extends AbstractEntity {
public static final int ENCRYPTION_NONE = 0;
public static final int ENCRYPTION_PGP = 1;
public static final int ENCRYPTION_OTR = 2;
+ public static final int ENCRYPTION_DECRYPTED = 3;
public static String CONVERSATION = "conversationUuid";
public static String COUNTERPART = "counterpart";
@@ -136,5 +137,8 @@ public class Message extends AbstractEntity {
public void setEncryption(int encryption) {
this.encryption = encryption;
}
-
+
+ public void setBody(String body) {
+ this.body = body;
+ }
}
diff --git a/src/de/gultsch/chat/services/XmppConnectionService.java b/src/de/gultsch/chat/services/XmppConnectionService.java
index ee8fd9cb9..a2b85f624 100644
--- a/src/de/gultsch/chat/services/XmppConnectionService.java
+++ b/src/de/gultsch/chat/services/XmppConnectionService.java
@@ -1,5 +1,16 @@
package de.gultsch.chat.services;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+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.io.UnsupportedEncodingException;
+import java.io.Writer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -8,11 +19,21 @@ import java.util.Hashtable;
import java.util.List;
import java.util.Set;
+import org.json.JSONException;
+import org.openintents.openpgp.IOpenPgpService;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.openintents.openpgp.util.OpenPgpServiceConnection;
+import org.openintents.openpgp.OpenPgpError;
+
import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus;
+import de.gultsch.chat.crypto.PgpEngine;
+import de.gultsch.chat.crypto.PgpEngine.OpenPgpException;
+import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
@@ -46,7 +67,9 @@ import android.database.DatabaseUtils;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.util.Log;
@@ -84,7 +107,12 @@ public class XmppConnectionService extends Service {
Message message = null;
boolean notify = false;
if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
- if (packet.hasChild("body")
+ String pgpBody = MessageParser.getPgpBody(packet);
+ if (pgpBody != null) {
+ message = MessageParser.parsePgpChat(pgpBody, packet,
+ account, service);
+ notify = false;
+ } else if (packet.hasChild("body")
&& (packet.getBody().startsWith("?OTR"))) {
message = MessageParser.parseOtrChat(packet, account,
service);
@@ -164,6 +192,13 @@ public class XmppConnectionService extends Service {
if (convChangedListener != null) {
convChangedListener.onConversationListChanged();
}
+ if (account.getKeys().has("pgp_signature")) {
+ try {
+ sendPgpPresence(account, account.getKeys().getString("pgp_signature"));
+ } catch (JSONException e) {
+ //
+ }
+ }
}
}
};
@@ -195,6 +230,18 @@ public class XmppConnectionService extends Service {
} else if (show.getContent().equals("dnd")) {
contact.updatePresence(fromParts[1], Presences.DND);
}
+ Element x = packet.findChild("x");
+ if ((x != null)
+ && (x.getAttribute("xmlns").equals("jabber:x:signed"))) {
+ try {
+ Log.d(LOGTAG,"pgp signature for contact" +packet.getAttribute("from"));
+ contact.setPgpKeyId(getPgpEngine().fetchKeyId(packet.findChild("status")
+ .getContent(), x.getContent()));
+ databaseBackend.updateContact(contact);
+ } catch (OpenPgpException e) {
+ Log.d(LOGTAG,"faulty pgp. just ignore");
+ }
+ }
databaseBackend.updateContact(contact);
} else if (type.equals("unavailable")) {
if (fromParts.length != 2) {
@@ -242,6 +289,23 @@ public class XmppConnectionService extends Service {
}
};
+ private OpenPgpServiceConnection pgpServiceConnection;
+ private PgpEngine mPgpEngine = null;
+
+ public PgpEngine getPgpEngine() {
+ if (pgpServiceConnection.isBound()) {
+ if (this.mPgpEngine == null) {
+ this.mPgpEngine = new PgpEngine(new OpenPgpApi(
+ getApplicationContext(),
+ pgpServiceConnection.getService()));
+ }
+ return mPgpEngine;
+ } else {
+ return null;
+ }
+
+ }
+
private void processRosterItems(Account account, Element elements) {
for (Element item : elements.getChildren()) {
if (item.getName().equals("item")) {
@@ -307,6 +371,9 @@ public class XmppConnectionService extends Service {
getContentResolver().registerContentObserver(
ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
+ this.pgpServiceConnection = new OpenPgpServiceConnection(
+ getApplicationContext(), "org.sufficientlysecure.keychain");
+ this.pgpServiceConnection.bindToService();
}
@Override
@@ -332,7 +399,8 @@ public class XmppConnectionService extends Service {
return connection;
}
- public void sendMessage(Account account, Message message, String presence) {
+ public void sendMessage(Message message, String presence) {
+ Account account = message.getConversation().getAccount();
Conversation conv = message.getConversation();
boolean saveInDb = false;
boolean addToConversation = false;
@@ -352,6 +420,25 @@ public class XmppConnectionService extends Service {
}
saveInDb = true;
addToConversation = true;
+ } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ long keyId = message.getConversation().getContact()
+ .getPgpKeyId();
+ packet = new MessagePacket();
+ packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setFrom(message.getConversation().getAccount()
+ .getFullJid());
+ packet.setTo(message.getCounterpart());
+ packet.setBody("This is an XEP-0027 encryted message");
+ Element x = new Element("x");
+ x.setAttribute("xmlns", "jabber:x:encrypted");
+ x.setContent(this.getPgpEngine().encrypt(keyId,
+ message.getBody()));
+ packet.addChild(x);
+ account.getXmppConnection().sendMessagePacket(packet);
+ message.setStatus(Message.STATUS_SEND);
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ saveInDb = true;
+ addToConversation = true;
} else {
// don't encrypt
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
@@ -777,6 +864,10 @@ public class XmppConnectionService extends Service {
databaseBackend.updateContact(contact);
}
+ public void updateMessage(Message message) {
+ databaseBackend.updateMessage(message);
+ }
+
public void createContact(Contact contact) {
SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
@@ -841,4 +932,27 @@ public class XmppConnectionService extends Service {
Log.d(LOGTAG, packet.toString());
contact.getAccount().getXmppConnection().sendPresencePacket(packet);
}
+
+ public void sendPgpPresence(Account account, String signature) {
+ PresencePacket packet = new PresencePacket();
+ packet.setAttribute("from", account.getFullJid());
+ Element status = new Element("status");
+ status.setContent("online");
+ packet.addChild(status);
+ Element x = new Element("x");
+ x.setAttribute("xmlns", "jabber:x:signed");
+ x.setContent(signature);
+ packet.addChild(x);
+ account.getXmppConnection().sendPresencePacket(packet);
+ }
+
+ public void generatePgpAnnouncement(Account account)
+ throws PgpEngine.UserInputRequiredException {
+ if (account.getStatus() == Account.STATUS_ONLINE) {
+ String signature = getPgpEngine().generateSignature("online");
+ account.setKey("pgp_signature", signature);
+ databaseBackend.updateAccount(account);
+ sendPgpPresence(account, signature);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/de/gultsch/chat/ui/ConversationActivity.java b/src/de/gultsch/chat/ui/ConversationActivity.java
index f9a924ecf..7fbc432e3 100644
--- a/src/de/gultsch/chat/ui/ConversationActivity.java
+++ b/src/de/gultsch/chat/ui/ConversationActivity.java
@@ -5,8 +5,13 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.openintents.openpgp.util.OpenPgpConstants;
+
import de.gultsch.chat.R;
import de.gultsch.chat.R.id;
+import de.gultsch.chat.crypto.PgpEngine;
+import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
@@ -20,6 +25,7 @@ import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
import android.graphics.Typeface;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
@@ -45,7 +51,8 @@ public class ConversationActivity extends XmppActivity {
public static final String VIEW_CONVERSATION = "viewConversation";
public static final String CONVERSATION = "conversationUuid";
- public static final int INSERT_CONTACT = 0x9889;
+ public static final int REQUEST_SEND_MESSAGE = 0x75441;
+ public static final int REQUEST_DECRYPT_PGP = 0x76783;
protected SlidingPaneLayout spl;
@@ -354,6 +361,9 @@ public class ConversationActivity extends XmppActivity {
case Message.ENCRYPTION_PGP:
popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
break;
+ case Message.ENCRYPTION_DECRYPTED:
+ popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
+ break;
default:
popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
break;
@@ -459,11 +469,4 @@ public class ConversationActivity extends XmppActivity {
}
}
}
-
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode==INSERT_CONTACT) {
- Log.d("xmppService","contact inserted");
- this.contactInserted = true;
- }
- }
}
diff --git a/src/de/gultsch/chat/ui/ConversationFragment.java b/src/de/gultsch/chat/ui/ConversationFragment.java
index b3a6ad774..099f22537 100644
--- a/src/de/gultsch/chat/ui/ConversationFragment.java
+++ b/src/de/gultsch/chat/ui/ConversationFragment.java
@@ -3,17 +3,16 @@ package de.gultsch.chat.ui;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Hashtable;
+import java.util.LinkedList;
import java.util.List;
import java.util.Set;
-import javax.crypto.spec.PSource;
-
-import net.java.otr4j.OtrException;
import net.java.otr4j.session.SessionStatus;
import de.gultsch.chat.R;
+import de.gultsch.chat.crypto.PgpEngine.OpenPgpException;
+import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message;
@@ -23,18 +22,20 @@ import de.gultsch.chat.utils.UIHelper;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.DialogInterface;
+import android.content.IntentSender;
import android.content.SharedPreferences;
+import android.content.IntentSender.SendIntentException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
@@ -42,6 +43,7 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.ProgressBar;
import android.widget.TextView;
public class ConversationFragment extends Fragment {
@@ -54,27 +56,45 @@ public class ConversationFragment extends Fragment {
protected Contact contact;
protected BitmapCache mBitmapCache = new BitmapCache();
+ protected String queuedPqpMessage = null;
+
private EditText chatMsg;
-
+
protected Bitmap selfBitmap;
+
+ private IntentSender askForPassphraseIntent = null;
private OnClickListener sendMsgListener = new OnClickListener() {
@Override
public void onClick(View v) {
- ConversationActivity activity = (ConversationActivity) getActivity();
- final XmppConnectionService xmppService = activity.xmppConnectionService;
if (chatMsg.getText().length() < 1)
return;
Message message = new Message(conversation, chatMsg.getText()
.toString(), conversation.nextMessageEncryption);
if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) {
sendOtrMessage(message);
+ } else if (conversation.nextMessageEncryption == Message.ENCRYPTION_PGP) {
+ sendPgpMessage(message);
} else {
sendPlainTextMessage(message);
}
}
};
+ protected OnClickListener clickToDecryptListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Log.d("gultsch","clicked to decrypt");
+ if (askForPassphraseIntent!=null) {
+ try {
+ getActivity().startIntentSenderForResult(askForPassphraseIntent, ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0);
+ } catch (SendIntentException e) {
+ Log.d("gultsch","couldnt fire intent");
+ }
+ }
+ }
+ };
public void updateChatMsgHint() {
if (conversation.getMode() == Conversation.MODE_MULTI) {
@@ -89,6 +109,10 @@ public class ConversationFragment extends Fragment {
break;
case Message.ENCRYPTION_PGP:
chatMsg.setHint("Send openPGP encryted messeage");
+ break;
+ case Message.ENCRYPTION_DECRYPTED:
+ chatMsg.setHint("Send openPGP encryted messeage");
+ break;
default:
break;
}
@@ -107,9 +131,9 @@ public class ConversationFragment extends Fragment {
ImageButton sendButton = (ImageButton) view
.findViewById(R.id.textSendButton);
sendButton.setOnClickListener(this.sendMsgListener);
-
- messagesView = (ListView) view.findViewById(R.id.messages_view);
+ messagesView = (ListView) view.findViewById(R.id.messages_view);
+
messageListAdapter = new ArrayAdapter(this.getActivity()
.getApplicationContext(), R.layout.message_sent,
this.messageList) {
@@ -155,23 +179,28 @@ public class ConversationFragment extends Fragment {
viewHolder.imageView = (ImageView) view
.findViewById(R.id.message_photo);
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
- Uri uri = item.getConversation().getProfilePhotoUri();
+ Uri uri = item.getConversation()
+ .getProfilePhotoUri();
if (uri != null) {
viewHolder.imageView
.setImageBitmap(mBitmapCache.get(item
- .getConversation().getName(), uri));
+ .getConversation().getName(),
+ uri));
} else {
viewHolder.imageView
.setImageBitmap(mBitmapCache.get(item
- .getConversation().getName(), null));
+ .getConversation().getName(),
+ null));
}
}
break;
case ERROR:
- view = (View) inflater.inflate(R.layout.message_error, null);
+ view = (View) inflater.inflate(R.layout.message_error,
+ null);
viewHolder.imageView = (ImageView) view
.findViewById(R.id.message_photo);
- viewHolder.imageView.setImageBitmap(mBitmapCache.getError());
+ viewHolder.imageView.setImageBitmap(mBitmapCache
+ .getError());
break;
default:
viewHolder = null;
@@ -186,7 +215,7 @@ public class ConversationFragment extends Fragment {
viewHolder = (ViewHolder) view.getTag();
}
if (type == RECIEVED) {
- if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
+ if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
if (item.getCounterpart() != null) {
viewHolder.imageView.setImageBitmap(mBitmapCache
.get(item.getCounterpart(), null));
@@ -199,7 +228,15 @@ public class ConversationFragment extends Fragment {
}
String body = item.getBody();
if (body != null) {
- viewHolder.messageBody.setText(body.trim());
+ if (item.getEncryption() == Message.ENCRYPTION_PGP) {
+ viewHolder.messageBody.setText(getString(R.string.encrypted_message));
+ viewHolder.messageBody.setTextColor(0xff33B5E5);
+ viewHolder.messageBody.setOnClickListener(clickToDecryptListener);
+ } else {
+ viewHolder.messageBody.setText(body.trim());
+ viewHolder.messageBody.setTextColor(0xff000000);
+ viewHolder.messageBody.setOnClickListener(null);
+ }
}
if (item.getStatus() == Message.STATUS_UNSEND) {
viewHolder.time.setTypeface(null, Typeface.ITALIC);
@@ -236,7 +273,7 @@ public class ConversationFragment extends Fragment {
if (showPhoneSelfContactPicture) {
Uri selfiUri = PhoneHelper.getSefliUri(getActivity());
- if (selfiUri!=null) {
+ if (selfiUri != null) {
try {
self = BitmapFactory.decodeStream(getActivity()
.getContentResolver().openInputStream(selfiUri));
@@ -245,7 +282,7 @@ public class ConversationFragment extends Fragment {
}
}
}
- if (self==null){
+ if (self == null) {
self = UIHelper.getUnknownContactPicture(conversation.getAccount()
.getJid(), 200);
}
@@ -280,17 +317,38 @@ public class ConversationFragment extends Fragment {
activity.updateConversationList();
}
}
+ if (queuedPqpMessage != null) {
+ this.conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
+ Message message = new Message(conversation, queuedPqpMessage,
+ Message.ENCRYPTION_PGP);
+ sendPgpMessage(message);
+ }
}
public void updateMessages() {
ConversationActivity activity = (ConversationActivity) getActivity();
+ List encryptedMessages = new LinkedList();
+ for(Message message : this.conversation.getMessages()) {
+ if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ encryptedMessages.add(message);
+ }
+ }
+ if (encryptedMessages.size() > 0) {
+ DecryptMessage task = new DecryptMessage();
+ Message[] msgs = new Message[encryptedMessages.size()];
+ task.execute(encryptedMessages.toArray(msgs));
+ }
this.messageList.clear();
this.messageList.addAll(this.conversation.getMessages());
this.messageListAdapter.notifyDataSetChanged();
if (messageList.size() >= 1) {
int latestEncryption = this.conversation.getLatestMessage()
.getEncryption();
- conversation.nextMessageEncryption = latestEncryption;
+ if (latestEncryption== Message.ENCRYPTION_DECRYPTED) {
+ conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
+ } else {
+ conversation.nextMessageEncryption = latestEncryption;
+ }
makeFingerprintWarning(latestEncryption);
}
getActivity().invalidateOptionsMenu();
@@ -339,17 +397,47 @@ public class ConversationFragment extends Fragment {
protected void sendPlainTextMessage(Message message) {
ConversationActivity activity = (ConversationActivity) getActivity();
- activity.xmppConnectionService.sendMessage(conversation.getAccount(),
- message, null);
+ activity.xmppConnectionService.sendMessage(message, null);
chatMsg.setText("");
}
+ protected void sendPgpMessage(final Message message) {
+ ConversationActivity activity = (ConversationActivity) getActivity();
+ final XmppConnectionService xmppService = activity.xmppConnectionService;
+ Contact contact = message.getConversation().getContact();
+ if (contact.getPgpKeyId() != 0) {
+ xmppService.sendMessage(message, null);
+ chatMsg.setText("");
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle("No openPGP key found");
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage("There is no openPGP key assoziated with this contact");
+ builder.setNegativeButton("Cancel", null);
+ builder.setPositiveButton("Send plain text",
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.sendMessage(message, null);
+ chatMsg.setText("");
+ }
+ });
+ builder.create().show();
+ }
+ }
+
+ public void resendPgpMessage(String msg) {
+ this.queuedPqpMessage = msg;
+ }
+
protected void sendOtrMessage(final Message message) {
ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
if (conversation.hasValidOtrSession()) {
- activity.xmppConnectionService.sendMessage(
- conversation.getAccount(), message, null);
+ activity.xmppConnectionService.sendMessage(message, null);
chatMsg.setText("");
} else {
Hashtable presences;
@@ -372,17 +460,15 @@ public class ConversationFragment extends Fragment {
int which) {
conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
message.setEncryption(Message.ENCRYPTION_NONE);
- xmppService.sendMessage(
- conversation.getAccount(), message,
- null);
+ xmppService.sendMessage(message, null);
chatMsg.setText("");
}
});
builder.setNegativeButton("Cancel", null);
builder.create().show();
} else if (presences.size() == 1) {
- xmppService.sendMessage(conversation.getAccount(), message,
- (String) presences.keySet().toArray()[0]);
+ xmppService.sendMessage(message, (String) presences.keySet()
+ .toArray()[0]);
chatMsg.setText("");
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(
@@ -396,8 +482,7 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(DialogInterface dialog,
int which) {
- xmppService.sendMessage(
- conversation.getAccount(), message,
+ xmppService.sendMessage(message,
presencesArray[which]);
chatMsg.setText("");
}
@@ -438,7 +523,7 @@ public class ConversationFragment extends Fragment {
return bm;
}
}
-
+
public Bitmap getError() {
if (error == null) {
error = UIHelper.getErrorPicture(200);
@@ -446,4 +531,48 @@ public class ConversationFragment extends Fragment {
return error;
}
}
+
+ class DecryptMessage extends AsyncTask {
+
+ @Override
+ protected Boolean doInBackground(Message... params) {
+ XmppActivity activity = (XmppActivity) getActivity();
+ askForPassphraseIntent = null;
+ for(int i = 0; i < params.length; ++i) {
+ if (params[i].getEncryption() == Message.ENCRYPTION_PGP) {
+ String body = params[i].getBody();
+ String decrypted = null;
+ try {
+ if (activity==null) {
+ return false;
+ }
+ Log.d("gultsch","calling to decrypt message id #"+params[i].getUuid());
+ decrypted = activity.xmppConnectionService.getPgpEngine().decrypt(body);
+ } catch (UserInputRequiredException e) {
+ askForPassphraseIntent = e.getPendingIntent().getIntentSender();
+ return false;
+
+ } catch (OpenPgpException e) {
+ Log.d("gultsch","error decrypting pgp");
+ }
+ if (decrypted!=null) {
+ params[i].setBody(decrypted);
+ params[i].setEncryption(Message.ENCRYPTION_DECRYPTED);
+ activity.xmppConnectionService.updateMessage(params[i]);
+ }
+ if (activity!=null) {
+ activity.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ messageListAdapter.notifyDataSetChanged();
+ }
+ });
+ }
+ }
+ }
+ return true;
+ }
+
+ }
}
diff --git a/src/de/gultsch/chat/ui/DialogContactDetails.java b/src/de/gultsch/chat/ui/DialogContactDetails.java
index 8983a8730..324a7aacd 100644
--- a/src/de/gultsch/chat/ui/DialogContactDetails.java
+++ b/src/de/gultsch/chat/ui/DialogContactDetails.java
@@ -63,7 +63,7 @@ public class DialogContactDetails extends DialogFragment {
intent.putExtra(Intents.Insert.IM_HANDLE,contact.getJid());
intent.putExtra(Intents.Insert.IM_PROTOCOL,CommonDataKinds.Im.PROTOCOL_JABBER);
intent.putExtra("finishActivityOnSaveCompleted", true);
- getActivity().startActivityForResult(intent,ConversationActivity.INSERT_CONTACT);
+ getActivity().startActivityForResult(intent,0);
mDetailsDialog.dismiss();
}
};
diff --git a/src/de/gultsch/chat/ui/ManageAccountActivity.java b/src/de/gultsch/chat/ui/ManageAccountActivity.java
index 66a407820..bcc5306d8 100644
--- a/src/de/gultsch/chat/ui/ManageAccountActivity.java
+++ b/src/de/gultsch/chat/ui/ManageAccountActivity.java
@@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import de.gultsch.chat.R;
+import de.gultsch.chat.crypto.PgpEngine;
+import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.ui.EditAccount.EditAccountListener;
import android.app.Activity;
@@ -12,6 +14,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
@@ -31,6 +34,8 @@ import android.widget.TextView;
public class ManageAccountActivity extends XmppActivity implements ActionMode.Callback {
+ public static final int REQUEST_ANNOUNCE_PGP = 0x73731;
+
protected boolean isActionMode = false;
protected ActionMode actionMode;
protected Account selectedAccountForActionMode = null;
@@ -231,6 +236,17 @@ public class ManageAccountActivity extends XmppActivity implements ActionMode.Ca
});
builder.setNegativeButton("Cancel",null);
builder.create().show();
+ } else if (item.getItemId()==R.id.announce_pgp) {
+ mode.finish();
+ try {
+ xmppConnectionService.generatePgpAnnouncement(selectedAccountForActionMode);
+ } catch (PgpEngine.UserInputRequiredException e) {
+ try {
+ startIntentSenderForResult(e.getPendingIntent().getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
+ } catch (SendIntentException e1) {
+ Log.d("gultsch","sending intent failed");
+ }
+ }
}
return true;
}
@@ -279,4 +295,18 @@ public class ManageAccountActivity extends XmppActivity implements ActionMode.Ca
}
});
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_ANNOUNCE_PGP) {
+ try {
+ xmppConnectionService.generatePgpAnnouncement(selectedAccountForActionMode);
+ } catch (UserInputRequiredException e) {
+ Log.d("gultsch","already came back. ignoring");
+ }
+ }
+ }
+ }
}
diff --git a/src/de/gultsch/chat/utils/MessageParser.java b/src/de/gultsch/chat/utils/MessageParser.java
index 1dca73432..a12ef58c9 100644
--- a/src/de/gultsch/chat/utils/MessageParser.java
+++ b/src/de/gultsch/chat/utils/MessageParser.java
@@ -23,6 +23,12 @@ public class MessageParser {
return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED);
}
+ public static Message parsePgpChat(String pgpBody, MessagePacket packet, Account account, XmppConnectionService service) {
+ String[] fromParts = packet.getFrom().split("/");
+ Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
+ return new Message(conversation, packet.getFrom(), pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECIEVED);
+ }
+
public static Message parseOtrChat(MessagePacket packet, Account account, XmppConnectionService service) {
String[] fromParts = packet.getFrom().split("/");
Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
@@ -131,4 +137,13 @@ public class MessageParser {
}
return new Message(conversation, packet.getFrom(), displayError, Message.ENCRYPTION_NONE, Message.STATUS_ERROR);
}
+
+ public static String getPgpBody(MessagePacket packet) {
+ for(Element child : packet.getChildren()) {
+ if (child.getName().equals("x")&&child.getAttribute("xmlns").equals("jabber:x:encrypted")) {
+ return child.getContent();
+ }
+ }
+ return null;
+ }
}