Merge branch 'CryptoNextBeta' into development
* CryptoNextBeta: (60 commits) Lock TrustKeys if no trusted keys are available Optimize imports Use MD style for key trust toggle switch Fix set/remove OnUpdateBlocklistListener Fix axolotl database migration Remove device list from EditAccount Add clear devices to overflow menu in EditAccount Ask for key trust when sending messages Encrypt files for HTTP upload in encrypted chats Refactor trust key ui and show in account details Send correct body for HTTP files Handle file transmission properly in axolotl Remove unneccessary code Fix trust status for outgoing messages Don't merge messages with different trust statuses Fix copying of axolotl keys to clipboard Add refresh icon to v21 theme Disable Axolotl option if not usable Show trust status of messages' originating session Add key trust toggle to ContactDetailsActivity ...
@ -37,6 +37,8 @@ dependencies {
|
||||
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
||||
compile 'me.leolin:ShortcutBadger:1.1.1@aar'
|
||||
compile 'com.kyleduo.switchbutton:library:1.2.8'
|
||||
compile 'org.whispersystems:axolotl-android:1.3.4'
|
||||
compile 'com.kyleduo.switchbutton:library:1.2.8'
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -130,6 +130,10 @@
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.TrustKeysActivity"
|
||||
android:label="@string/trust_keys"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"/>
|
||||
<activity
|
||||
android:name="de.duenndns.ssl.MemorizingActivity"
|
||||
android:theme="@style/ConversationsTheme"
|
||||
|
@ -1,5 +1,20 @@
|
||||
package eu.siacs.conversations.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import net.java.otr4j.OtrEngineHost;
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.OtrPolicy;
|
||||
import net.java.otr4j.OtrPolicyImpl;
|
||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
||||
import net.java.otr4j.crypto.OtrCryptoException;
|
||||
import net.java.otr4j.session.FragmenterInstructions;
|
||||
import net.java.otr4j.session.InstanceTag;
|
||||
import net.java.otr4j.session.SessionID;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
@ -11,31 +26,15 @@ import java.security.spec.DSAPrivateKeySpec;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
import net.java.otr4j.OtrEngineHost;
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.OtrPolicy;
|
||||
import net.java.otr4j.OtrPolicyImpl;
|
||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
||||
import net.java.otr4j.crypto.OtrCryptoException;
|
||||
import net.java.otr4j.session.InstanceTag;
|
||||
import net.java.otr4j.session.SessionID;
|
||||
import net.java.otr4j.session.FragmenterInstructions;
|
||||
|
||||
public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||
|
||||
private Account account;
|
||||
|
@ -1,5 +1,13 @@
|
||||
package eu.siacs.conversations.crypto;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
@ -9,10 +17,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
@ -22,9 +26,6 @@ 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 android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
public class PgpEngine {
|
||||
private OpenPgpApi api;
|
||||
|
@ -0,0 +1,4 @@
|
||||
package eu.siacs.conversations.crypto.axolotl;
|
||||
|
||||
public class NoSessionsCreatedException extends Throwable{
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
package eu.siacs.conversations.crypto.axolotl;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
public class XmppAxolotlMessage {
|
||||
private byte[] innerKey;
|
||||
private byte[] ciphertext;
|
||||
private byte[] iv;
|
||||
private final Set<XmppAxolotlMessageHeader> headers;
|
||||
private final Jid from;
|
||||
private final int sourceDeviceId;
|
||||
|
||||
public static class XmppAxolotlMessageHeader {
|
||||
private final int recipientDeviceId;
|
||||
private final byte[] content;
|
||||
|
||||
public XmppAxolotlMessageHeader(int deviceId, byte[] content) {
|
||||
this.recipientDeviceId = deviceId;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public XmppAxolotlMessageHeader(Element header) {
|
||||
if("header".equals(header.getName())) {
|
||||
this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid"));
|
||||
this.content = Base64.decode(header.getContent(),Base64.DEFAULT);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Argument not a <header> Element!");
|
||||
}
|
||||
}
|
||||
|
||||
public int getRecipientDeviceId() {
|
||||
return recipientDeviceId;
|
||||
}
|
||||
|
||||
public byte[] getContents() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public Element toXml() {
|
||||
Element headerElement = new Element("header");
|
||||
// TODO: generate XML
|
||||
headerElement.setAttribute("rid", getRecipientDeviceId());
|
||||
headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT));
|
||||
return headerElement;
|
||||
}
|
||||
}
|
||||
|
||||
public static class XmppAxolotlPlaintextMessage {
|
||||
private final AxolotlService.XmppAxolotlSession session;
|
||||
private final String plaintext;
|
||||
private final String fingerprint;
|
||||
|
||||
public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) {
|
||||
this.session = session;
|
||||
this.plaintext = plaintext;
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public String getPlaintext() {
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
public AxolotlService.XmppAxolotlSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
public XmppAxolotlMessage(Jid from, Element axolotlMessage) {
|
||||
this.from = from;
|
||||
this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id"));
|
||||
this.headers = new HashSet<>();
|
||||
for(Element child:axolotlMessage.getChildren()) {
|
||||
switch(child.getName()) {
|
||||
case "header":
|
||||
headers.add(new XmppAxolotlMessageHeader(child));
|
||||
break;
|
||||
case "message":
|
||||
iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
|
||||
ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) {
|
||||
this.from = from;
|
||||
this.sourceDeviceId = sourceDeviceId;
|
||||
this.headers = new HashSet<>();
|
||||
this.encrypt(plaintext);
|
||||
}
|
||||
|
||||
private void encrypt(String plaintext) {
|
||||
try {
|
||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
||||
generator.init(128);
|
||||
SecretKey secretKey = generator.generateKey();
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
this.innerKey = secretKey.getEncoded();
|
||||
this.iv = cipher.getIV();
|
||||
this.ciphertext = cipher.doFinal(plaintext.getBytes());
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| IllegalBlockSizeException | BadPaddingException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Jid getFrom() {
|
||||
return this.from;
|
||||
}
|
||||
|
||||
public int getSenderDeviceId() {
|
||||
return sourceDeviceId;
|
||||
}
|
||||
|
||||
public byte[] getCiphertext() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public Set<XmppAxolotlMessageHeader> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void addHeader(XmppAxolotlMessageHeader header) {
|
||||
headers.add(header);
|
||||
}
|
||||
|
||||
public byte[] getInnerKey(){
|
||||
return innerKey;
|
||||
}
|
||||
|
||||
public byte[] getIV() {
|
||||
return this.iv;
|
||||
}
|
||||
|
||||
public Element toXml() {
|
||||
// TODO: generate outer XML, add in header XML
|
||||
Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX);
|
||||
message.setAttribute("id", sourceDeviceId);
|
||||
for(XmppAxolotlMessageHeader header: headers) {
|
||||
message.addChild(header.toXml());
|
||||
}
|
||||
Element payload = message.addChild("message");
|
||||
payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
|
||||
payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) {
|
||||
XmppAxolotlPlaintextMessage plaintextMessage = null;
|
||||
try {
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
|
||||
String plaintext = new String(cipher.doFinal(ciphertext));
|
||||
plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint);
|
||||
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||
| BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return plaintextMessage;
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ 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;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
@ -122,6 +123,7 @@ public class Account extends AbstractEntity {
|
||||
protected String avatar;
|
||||
protected boolean online = false;
|
||||
private OtrService mOtrService = null;
|
||||
private AxolotlService axolotlService = null;
|
||||
private XmppConnection xmppConnection = null;
|
||||
private long mEndGracePeriod = 0L;
|
||||
private String otrFingerprint;
|
||||
@ -254,6 +256,10 @@ public class Account extends AbstractEntity {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public String getKey(final String name) {
|
||||
return this.keys.optString(name, null);
|
||||
}
|
||||
|
||||
public boolean setKey(final String keyName, final String keyValue) {
|
||||
try {
|
||||
this.keys.put(keyName, keyValue);
|
||||
@ -277,8 +283,13 @@ public class Account extends AbstractEntity {
|
||||
return values;
|
||||
}
|
||||
|
||||
public AxolotlService getAxolotlService() {
|
||||
return axolotlService;
|
||||
}
|
||||
|
||||
public void initAccountServices(final XmppConnectionService context) {
|
||||
this.mOtrService = new OtrService(context, this);
|
||||
this.axolotlService = new AxolotlService(this, context);
|
||||
}
|
||||
|
||||
public OtrService getOtrService() {
|
||||
|
@ -183,6 +183,7 @@ public class Contact implements ListItem, Blockable {
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
synchronized (this.keys) {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ACCOUNT, accountUuid);
|
||||
values.put(SYSTEMNAME, systemName);
|
||||
@ -198,6 +199,7 @@ public class Contact implements ListItem, Blockable {
|
||||
values.put(GROUPS, groups.toString());
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
public int getSubscription() {
|
||||
return this.subscription;
|
||||
@ -281,6 +283,7 @@ public class Contact implements ListItem, Blockable {
|
||||
}
|
||||
|
||||
public ArrayList<String> getOtrFingerprints() {
|
||||
synchronized (this.keys) {
|
||||
final ArrayList<String> fingerprints = new ArrayList<String>();
|
||||
try {
|
||||
if (this.keys.has("otr_fingerprints")) {
|
||||
@ -297,8 +300,9 @@ public class Contact implements ListItem, Blockable {
|
||||
}
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
}
|
||||
public boolean addOtrFingerprint(String print) {
|
||||
synchronized (this.keys) {
|
||||
if (getOtrFingerprints().contains(print)) {
|
||||
return false;
|
||||
}
|
||||
@ -306,7 +310,6 @@ public class Contact implements ListItem, Blockable {
|
||||
JSONArray fingerprints;
|
||||
if (!this.keys.has("otr_fingerprints")) {
|
||||
fingerprints = new JSONArray();
|
||||
|
||||
} else {
|
||||
fingerprints = this.keys.getJSONArray("otr_fingerprints");
|
||||
}
|
||||
@ -317,8 +320,10 @@ public class Contact implements ListItem, Blockable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getPgpKeyId() {
|
||||
synchronized (this.keys) {
|
||||
if (this.keys.has("pgp_keyid")) {
|
||||
try {
|
||||
return this.keys.getLong("pgp_keyid");
|
||||
@ -329,12 +334,14 @@ public class Contact implements ListItem, Blockable {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPgpKeyId(long keyId) {
|
||||
synchronized (this.keys) {
|
||||
try {
|
||||
this.keys.put("pgp_keyid", keyId);
|
||||
} catch (final JSONException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,6 +448,7 @@ public class Contact implements ListItem, Blockable {
|
||||
}
|
||||
|
||||
public boolean deleteOtrFingerprint(String fingerprint) {
|
||||
synchronized (this.keys) {
|
||||
boolean success = false;
|
||||
try {
|
||||
if (this.keys.has("otr_fingerprints")) {
|
||||
@ -461,6 +469,7 @@ public class Contact implements ListItem, Blockable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean trusted() {
|
||||
return getOption(Options.FROM) && getOption(Options.TO);
|
||||
|
@ -179,11 +179,11 @@ public class Conversation extends AbstractEntity implements Blockable {
|
||||
}
|
||||
}
|
||||
|
||||
public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) {
|
||||
public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) {
|
||||
synchronized (this.messages) {
|
||||
for (Message message : this.messages) {
|
||||
if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
|
||||
&& (message.getEncryption() == Message.ENCRYPTION_OTR)) {
|
||||
&& (message.getEncryption() == encryptionType)) {
|
||||
onMessageFound.onMessageFound(message);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
@ -22,8 +23,6 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.utils.MimeUtils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class DownloadableFile extends File {
|
||||
|
||||
private static final long serialVersionUID = 2247012619505115863L;
|
||||
|
@ -8,6 +8,7 @@ import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.utils.GeoHelper;
|
||||
import eu.siacs.conversations.utils.MimeUtils;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
@ -34,6 +35,7 @@ public class Message extends AbstractEntity {
|
||||
public static final int ENCRYPTION_OTR = 2;
|
||||
public static final int ENCRYPTION_DECRYPTED = 3;
|
||||
public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
|
||||
public static final int ENCRYPTION_AXOLOTL = 5;
|
||||
|
||||
public static final int TYPE_TEXT = 0;
|
||||
public static final int TYPE_IMAGE = 1;
|
||||
@ -52,6 +54,7 @@ public class Message extends AbstractEntity {
|
||||
public static final String REMOTE_MSG_ID = "remoteMsgId";
|
||||
public static final String SERVER_MSG_ID = "serverMsgId";
|
||||
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
|
||||
public static final String FINGERPRINT = "axolotl_fingerprint";
|
||||
public static final String ME_COMMAND = "/me ";
|
||||
|
||||
|
||||
@ -73,6 +76,7 @@ public class Message extends AbstractEntity {
|
||||
protected Transferable transferable = null;
|
||||
private Message mNextMessage = null;
|
||||
private Message mPreviousMessage = null;
|
||||
private String axolotlFingerprint = null;
|
||||
|
||||
private Message() {
|
||||
|
||||
@ -94,6 +98,7 @@ public class Message extends AbstractEntity {
|
||||
TYPE_TEXT,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
this.conversation = conversation;
|
||||
}
|
||||
@ -101,7 +106,7 @@ public class Message extends AbstractEntity {
|
||||
private Message(final String uuid, final String conversationUUid, final Jid counterpart,
|
||||
final Jid trueCounterpart, final String body, final long timeSent,
|
||||
final int encryption, final int status, final int type, final String remoteMsgId,
|
||||
final String relativeFilePath, final String serverMsgId) {
|
||||
final String relativeFilePath, final String serverMsgId, final String fingerprint) {
|
||||
this.uuid = uuid;
|
||||
this.conversationUuid = conversationUUid;
|
||||
this.counterpart = counterpart;
|
||||
@ -114,6 +119,7 @@ public class Message extends AbstractEntity {
|
||||
this.remoteMsgId = remoteMsgId;
|
||||
this.relativeFilePath = relativeFilePath;
|
||||
this.serverMsgId = serverMsgId;
|
||||
this.axolotlFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public static Message fromCursor(Cursor cursor) {
|
||||
@ -150,7 +156,8 @@ public class Message extends AbstractEntity {
|
||||
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
||||
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
|
||||
cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
|
||||
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)));
|
||||
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
|
||||
cursor.getString(cursor.getColumnIndex(FINGERPRINT)));
|
||||
}
|
||||
|
||||
public static Message createStatusMessage(Conversation conversation, String body) {
|
||||
@ -184,6 +191,7 @@ public class Message extends AbstractEntity {
|
||||
values.put(REMOTE_MSG_ID, remoteMsgId);
|
||||
values.put(RELATIVE_FILE_PATH, relativeFilePath);
|
||||
values.put(SERVER_MSG_ID, serverMsgId);
|
||||
values.put(FINGERPRINT, axolotlFingerprint);
|
||||
return values;
|
||||
}
|
||||
|
||||
@ -391,7 +399,8 @@ public class Message extends AbstractEntity {
|
||||
!message.getBody().startsWith(ME_COMMAND) &&
|
||||
!this.getBody().startsWith(ME_COMMAND) &&
|
||||
!this.bodyIsHeart() &&
|
||||
!message.bodyIsHeart()
|
||||
!message.bodyIsHeart() &&
|
||||
this.isTrusted() == message.isTrusted()
|
||||
);
|
||||
}
|
||||
|
||||
@ -663,4 +672,17 @@ public class Message extends AbstractEntity {
|
||||
public int width = 0;
|
||||
public int height = 0;
|
||||
}
|
||||
|
||||
public void setAxolotlFingerprint(String fingerprint) {
|
||||
this.axolotlFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public String getAxolotlFingerprint() {
|
||||
return axolotlFingerprint;
|
||||
}
|
||||
|
||||
public boolean isTrusted() {
|
||||
return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint)
|
||||
== AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@ -11,8 +13,6 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public class MucOptions {
|
||||
|
||||
|
@ -12,6 +12,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.PhoneHelper;
|
||||
|
||||
@ -28,7 +29,8 @@ public abstract class AbstractGenerator {
|
||||
"urn:xmpp:avatar:metadata+notify",
|
||||
"urn:xmpp:ping",
|
||||
"jabber:iq:version",
|
||||
"http://jabber.org/protocol/chatstates"};
|
||||
"http://jabber.org/protocol/chatstates",
|
||||
AxolotlService.PEP_DEVICE_LIST+"+notify"};
|
||||
private final String[] MESSAGE_CONFIRMATION_FEATURES = {
|
||||
"urn:xmpp:chat-markers:0",
|
||||
"urn:xmpp:receipts"
|
||||
|
@ -1,15 +1,23 @@
|
||||
package eu.siacs.conversations.generator;
|
||||
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.services.MessageArchiveService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.PhoneHelper;
|
||||
import eu.siacs.conversations.utils.Xmlns;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.forms.Data;
|
||||
@ -115,6 +123,56 @@ public class IqGenerator extends AbstractGenerator {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveDeviceIds(final Jid to) {
|
||||
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
|
||||
if(to != null) {
|
||||
packet.setTo(to);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
||||
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null);
|
||||
if(to != null) {
|
||||
packet.setTo(to);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket publishDeviceIds(final Set<Integer> ids) {
|
||||
final Element item = new Element("item");
|
||||
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
|
||||
for(Integer id:ids) {
|
||||
final Element device = new Element("device");
|
||||
device.setAttribute("id", id);
|
||||
list.addChild(device);
|
||||
}
|
||||
return publish(AxolotlService.PEP_DEVICE_LIST, item);
|
||||
}
|
||||
|
||||
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
|
||||
final Set<PreKeyRecord> preKeyRecords, final int deviceId) {
|
||||
final Element item = new Element("item");
|
||||
final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
|
||||
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
|
||||
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
|
||||
ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
|
||||
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT));
|
||||
final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
|
||||
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT));
|
||||
final Element identityKeyElement = bundle.addChild("identityKey");
|
||||
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
|
||||
|
||||
final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
|
||||
for(PreKeyRecord preKeyRecord:preKeyRecords) {
|
||||
final Element prekey = prekeys.addChild("preKeyPublic");
|
||||
prekey.setAttribute("preKeyId", preKeyRecord.getId());
|
||||
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
|
||||
}
|
||||
|
||||
return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item);
|
||||
}
|
||||
|
||||
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
final Element query = packet.query("urn:xmpp:mam:0");
|
||||
|
@ -1,12 +1,18 @@
|
||||
package eu.siacs.conversations.generator;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.session.Session;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.session.Session;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
@ -59,6 +65,22 @@ public class MessageGenerator extends AbstractGenerator {
|
||||
delay.setAttribute("stamp", mDateFormat.format(date));
|
||||
}
|
||||
|
||||
public MessagePacket generateAxolotlChat(Message message) {
|
||||
return generateAxolotlChat(message, false);
|
||||
}
|
||||
|
||||
public MessagePacket generateAxolotlChat(Message message, boolean addDelay) {
|
||||
MessagePacket packet = preparePacket(message, addDelay);
|
||||
AxolotlService service = message.getConversation().getAccount().getAxolotlService();
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(message.getConversation().getAccount())+"Submitting message to axolotl service for send processing...");
|
||||
XmppAxolotlMessage axolotlMessage = service.encrypt(message);
|
||||
if (axolotlMessage == null) {
|
||||
return null;
|
||||
}
|
||||
packet.setAxolotlMessage(axolotlMessage.toXml());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generateOtrChat(Message message) {
|
||||
return generateOtrChat(message, false);
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
|
||||
@ -70,7 +70,8 @@ public class HttpDownloadConnection implements Transferable {
|
||||
String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null;
|
||||
if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
|
||||
this.message.setEncryption(Message.ENCRYPTION_PGP);
|
||||
} else if (message.getEncryption() != Message.ENCRYPTION_OTR) {
|
||||
} else if (message.getEncryption() != Message.ENCRYPTION_OTR
|
||||
&& message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
|
||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||
}
|
||||
String extension;
|
||||
@ -86,7 +87,8 @@ public class HttpDownloadConnection implements Transferable {
|
||||
this.file.setKey(CryptoHelper.hexToBytes(reference));
|
||||
}
|
||||
|
||||
if (this.message.getEncryption() == Message.ENCRYPTION_OTR
|
||||
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
|
||||
|| this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
|
||||
&& this.file.getKey() == null) {
|
||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.UiCallback;
|
||||
@ -88,7 +88,9 @@ public class HttpUploadConnection implements Transferable {
|
||||
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||
this.file.setExpectedSize(this.file.getSize());
|
||||
|
||||
if (Config.ENCRYPT_ON_HTTP_UPLOADED) {
|
||||
if (Config.ENCRYPT_ON_HTTP_UPLOADED
|
||||
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|
||||
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
this.key = new byte[48];
|
||||
mXmppConnectionService.getRNG().nextBytes(this.key);
|
||||
this.file.setKey(this.key);
|
||||
|
@ -11,7 +11,6 @@ import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
public abstract class AbstractParser {
|
||||
|
||||
|
@ -1,11 +1,25 @@
|
||||
package eu.siacs.conversations.parser;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
@ -71,6 +85,155 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||
return super.avatarData(items);
|
||||
}
|
||||
|
||||
public Element getItem(final IqPacket packet) {
|
||||
final Element pubsub = packet.findChild("pubsub",
|
||||
"http://jabber.org/protocol/pubsub");
|
||||
if (pubsub == null) {
|
||||
return null;
|
||||
}
|
||||
final Element items = pubsub.findChild("items");
|
||||
if (items == null) {
|
||||
return null;
|
||||
}
|
||||
return items.findChild("item");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Set<Integer> deviceIds(final Element item) {
|
||||
Set<Integer> deviceIds = new HashSet<>();
|
||||
if (item != null) {
|
||||
final Element list = item.findChild("list");
|
||||
if (list != null) {
|
||||
for (Element device : list.getChildren()) {
|
||||
if (!device.getName().equals("device")) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Integer id = Integer.valueOf(device.getAttribute("id"));
|
||||
deviceIds.add(id);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered nvalid <device> node in PEP:" + device.toString()
|
||||
+ ", skipping...");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceIds;
|
||||
}
|
||||
|
||||
public Integer signedPreKeyId(final Element bundle) {
|
||||
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||
if(signedPreKeyPublic == null) {
|
||||
return null;
|
||||
}
|
||||
return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId"));
|
||||
}
|
||||
|
||||
public ECPublicKey signedPreKeyPublic(final Element bundle) {
|
||||
ECPublicKey publicKey = null;
|
||||
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||
if(signedPreKeyPublic == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage());
|
||||
}
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public byte[] signedPreKeySignature(final Element bundle) {
|
||||
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
|
||||
if(signedPreKeySignature == null) {
|
||||
return null;
|
||||
}
|
||||
return Base64.decode(signedPreKeySignature.getContent(),Base64.DEFAULT);
|
||||
}
|
||||
|
||||
public IdentityKey identityKey(final Element bundle) {
|
||||
IdentityKey identityKey = null;
|
||||
final Element identityKeyElement = bundle.findChild("identityKey");
|
||||
if(identityKeyElement == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
|
||||
}
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
|
||||
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
|
||||
Element item = getItem(packet);
|
||||
if (item == null) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <item> in bundle IQ packet: " + packet);
|
||||
return null;
|
||||
}
|
||||
final Element bundleElement = item.findChild("bundle");
|
||||
if(bundleElement == null) {
|
||||
return null;
|
||||
}
|
||||
final Element prekeysElement = bundleElement.findChild("prekeys");
|
||||
if(prekeysElement == null) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <prekeys> in bundle IQ packet: " + packet);
|
||||
return null;
|
||||
}
|
||||
for(Element preKeyPublicElement : prekeysElement.getChildren()) {
|
||||
if(!preKeyPublicElement.getName().equals("preKeyPublic")){
|
||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
|
||||
continue;
|
||||
}
|
||||
Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
|
||||
try {
|
||||
ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
|
||||
preKeyRecords.put(preKeyId, preKeyPublic);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return preKeyRecords;
|
||||
}
|
||||
|
||||
public PreKeyBundle bundle(final IqPacket bundle) {
|
||||
Element bundleItem = getItem(bundle);
|
||||
if(bundleItem == null) {
|
||||
return null;
|
||||
}
|
||||
final Element bundleElement = bundleItem.findChild("bundle");
|
||||
if(bundleElement == null) {
|
||||
return null;
|
||||
}
|
||||
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
|
||||
Integer signedPreKeyId = signedPreKeyId(bundleElement);
|
||||
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
|
||||
IdentityKey identityKey = identityKey(bundleElement);
|
||||
if(signedPreKeyPublic == null || identityKey == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PreKeyBundle(0, 0, 0, null,
|
||||
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
|
||||
}
|
||||
|
||||
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
|
||||
List<PreKeyBundle> bundles = new ArrayList<>();
|
||||
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
||||
if ( preKeyPublics != null) {
|
||||
for (Integer preKeyId : preKeyPublics.keySet()) {
|
||||
ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
|
||||
bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
|
||||
0, null, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
return bundles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
||||
if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) {
|
||||
|
@ -6,7 +6,11 @@ import android.util.Pair;
|
||||
import net.java.otr4j.session.Session;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
@ -94,6 +98,20 @@ public class MessageParser extends AbstractParser implements
|
||||
}
|
||||
}
|
||||
|
||||
private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) {
|
||||
Message finishedMessage = null;
|
||||
AxolotlService service = conversation.getAccount().getAxolotlService();
|
||||
XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(from.toBareJid(), axolotlMessage);
|
||||
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
|
||||
if(plaintextMessage != null) {
|
||||
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
|
||||
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
|
||||
}
|
||||
|
||||
return finishedMessage;
|
||||
}
|
||||
|
||||
private class Invite {
|
||||
Jid jid;
|
||||
String password;
|
||||
@ -170,6 +188,13 @@ public class MessageParser extends AbstractParser implements
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
mXmppConnectionService.updateAccountUi();
|
||||
}
|
||||
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing...");
|
||||
Element item = items.findChild("item");
|
||||
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
||||
AxolotlService axolotlService = account.getAxolotlService();
|
||||
axolotlService.registerDevices(from, deviceIds);
|
||||
mXmppConnectionService.updateAccountUi();
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,8 +257,9 @@ public class MessageParser extends AbstractParser implements
|
||||
timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis());
|
||||
}
|
||||
final String body = packet.getBody();
|
||||
final String encrypted = packet.findChildContent("x", "jabber:x:encrypted");
|
||||
final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user");
|
||||
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
|
||||
final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX);
|
||||
int status;
|
||||
final Jid counterpart;
|
||||
final Jid to = packet.getTo();
|
||||
@ -261,11 +287,11 @@ public class MessageParser extends AbstractParser implements
|
||||
return;
|
||||
}
|
||||
|
||||
if (extractChatState(mXmppConnectionService.find(account,from), packet)) {
|
||||
if (extractChatState(mXmppConnectionService.find(account, from), packet)) {
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
|
||||
if ((body != null || encrypted != null) && !isMucStatusMessage) {
|
||||
if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) {
|
||||
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat);
|
||||
if (isTypeGroupChat) {
|
||||
if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
|
||||
@ -294,8 +320,13 @@ public class MessageParser extends AbstractParser implements
|
||||
} else {
|
||||
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
|
||||
}
|
||||
} else if (encrypted != null) {
|
||||
message = new Message(conversation, encrypted, Message.ENCRYPTION_PGP, status);
|
||||
} else if (pgpEncrypted != null) {
|
||||
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
|
||||
} else if (axolotlEncrypted != null) {
|
||||
message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status);
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
|
||||
}
|
||||
|
@ -1,10 +1,32 @@
|
||||
package eu.siacs.conversations.persistance;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
@ -13,19 +35,12 @@ import eu.siacs.conversations.entities.Roster;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
|
||||
private static DatabaseBackend instance = null;
|
||||
|
||||
private static final String DATABASE_NAME = "history";
|
||||
private static final int DATABASE_VERSION = 14;
|
||||
private static final int DATABASE_VERSION = 15;
|
||||
|
||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||
@ -39,6 +54,60 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
|
||||
+ Contact.JID + ") ON CONFLICT REPLACE);";
|
||||
|
||||
private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
|
||||
+ AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID
|
||||
+ ") ON CONFLICT REPLACE"
|
||||
+");";
|
||||
|
||||
private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
|
||||
+ AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID
|
||||
+ ") ON CONFLICT REPLACE"+
|
||||
");";
|
||||
|
||||
private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
|
||||
+ AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + ", "
|
||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID
|
||||
+ ") ON CONFLICT REPLACE"
|
||||
+");";
|
||||
|
||||
private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
|
||||
+ AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
|
||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + ", "
|
||||
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT
|
||||
+ ") ON CONFLICT IGNORE"
|
||||
+");";
|
||||
|
||||
private DatabaseBackend(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
@ -69,12 +138,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
|
||||
+ Message.RELATIVE_FILE_PATH + " TEXT, "
|
||||
+ Message.SERVER_MSG_ID + " TEXT, "
|
||||
+ Message.FINGERPRINT + " TEXT, "
|
||||
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
||||
+ Message.CONVERSATION + ") REFERENCES "
|
||||
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
||||
+ ") ON DELETE CASCADE);");
|
||||
|
||||
db.execSQL(CREATE_CONTATCS_STATEMENT);
|
||||
db.execSQL(CREATE_SESSIONS_STATEMENT);
|
||||
db.execSQL(CREATE_PREKEYS_STATEMENT);
|
||||
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
|
||||
db.execSQL(CREATE_IDENTITIES_STATEMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -215,6 +289,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
if (oldVersion < 15 && newVersion >= 15) {
|
||||
recreateAxolotlDb(db);
|
||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||
+ Message.FINGERPRINT + " TEXT");
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized DatabaseBackend getInstance(Context context) {
|
||||
@ -311,7 +390,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
};
|
||||
Cursor cursor = db.query(Conversation.TABLENAME, null,
|
||||
Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
|
||||
+ " like ? OR "+Conversation.CONTACTJID+"=?)", selectionArgs, null, null, null);
|
||||
+ " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
|
||||
if (cursor.getCount() == 0)
|
||||
return null;
|
||||
cursor.moveToFirst();
|
||||
@ -481,4 +560,403 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
cursor.close();
|
||||
return list;
|
||||
}
|
||||
|
||||
private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
|
||||
final SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] columns = null;
|
||||
String[] selectionArgs = {account.getUuid(),
|
||||
contact.getName(),
|
||||
Integer.toString(contact.getDeviceId())};
|
||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
columns,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
||||
selectionArgs,
|
||||
null, null, null);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public SessionRecord loadSession(Account account, AxolotlAddress contact) {
|
||||
SessionRecord session = null;
|
||||
Cursor cursor = getCursorForSession(account, contact);
|
||||
if(cursor.getCount() != 0) {
|
||||
cursor.moveToFirst();
|
||||
try {
|
||||
session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||
} catch (IOException e) {
|
||||
cursor.close();
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
return session;
|
||||
}
|
||||
|
||||
public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
|
||||
List<Integer> devices = new ArrayList<>();
|
||||
final SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID};
|
||||
String[] selectionArgs = {account.getUuid(),
|
||||
contact.getName()};
|
||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
columns,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
|
||||
selectionArgs,
|
||||
null, null, null);
|
||||
|
||||
while(cursor.moveToNext()) {
|
||||
devices.add(cursor.getInt(
|
||||
cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID)));
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
return devices;
|
||||
}
|
||||
|
||||
public boolean containsSession(Account account, AxolotlAddress contact) {
|
||||
Cursor cursor = getCursorForSession(account, contact);
|
||||
int count = cursor.getCount();
|
||||
cursor.close();
|
||||
return count != 0;
|
||||
}
|
||||
|
||||
public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName());
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||
db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
|
||||
}
|
||||
|
||||
public void deleteSession(Account account, AxolotlAddress contact) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = {account.getUuid(),
|
||||
contact.getName(),
|
||||
Integer.toString(contact.getDeviceId())};
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
||||
args);
|
||||
}
|
||||
|
||||
public void deleteAllSessions(Account account, AxolotlAddress contact) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = {account.getUuid(), contact.getName()};
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
|
||||
args);
|
||||
}
|
||||
|
||||
private Cursor getCursorForPreKey(Account account, int preKeyId) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
||||
String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
|
||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||
columns,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
||||
selectionArgs,
|
||||
null, null, null);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public PreKeyRecord loadPreKey(Account account, int preKeyId) {
|
||||
PreKeyRecord record = null;
|
||||
Cursor cursor = getCursorForPreKey(account, preKeyId);
|
||||
if(cursor.getCount() != 0) {
|
||||
cursor.moveToFirst();
|
||||
try {
|
||||
record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||
} catch (IOException e ) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
return record;
|
||||
}
|
||||
|
||||
public boolean containsPreKey(Account account, int preKeyId) {
|
||||
Cursor cursor = getCursorForPreKey(account, preKeyId);
|
||||
int count = cursor.getCount();
|
||||
cursor.close();
|
||||
return count != 0;
|
||||
}
|
||||
|
||||
public void storePreKey(Account account, PreKeyRecord record) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||
db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
|
||||
}
|
||||
|
||||
public void deletePreKey(Account account, int preKeyId) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = {account.getUuid(), Integer.toString(preKeyId)};
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
||||
args);
|
||||
}
|
||||
|
||||
private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
||||
String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||
columns,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
||||
selectionArgs,
|
||||
null, null, null);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
|
||||
SignedPreKeyRecord record = null;
|
||||
Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
|
||||
if(cursor.getCount() != 0) {
|
||||
cursor.moveToFirst();
|
||||
try {
|
||||
record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||
} catch (IOException e ) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
return record;
|
||||
}
|
||||
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
|
||||
List<SignedPreKeyRecord> prekeys = new ArrayList<>();
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
||||
String[] selectionArgs = {account.getUuid()};
|
||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||
columns,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?",
|
||||
selectionArgs,
|
||||
null, null, null);
|
||||
|
||||
while(cursor.moveToNext()) {
|
||||
try {
|
||||
prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
return prekeys;
|
||||
}
|
||||
|
||||
public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
|
||||
Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
|
||||
int count = cursor.getCount();
|
||||
cursor.close();
|
||||
return count != 0;
|
||||
}
|
||||
|
||||
public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||
db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
|
||||
}
|
||||
|
||||
public void deleteSignedPreKey(Account account, int signedPreKeyId) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
||||
args);
|
||||
}
|
||||
|
||||
private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
|
||||
return getIdentityKeyCursor(account, name, own, null);
|
||||
}
|
||||
|
||||
private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
|
||||
return getIdentityKeyCursor(account, null, null, fingerprint);
|
||||
}
|
||||
|
||||
private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) {
|
||||
final SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED,
|
||||
AxolotlService.SQLiteAxolotlStore.KEY};
|
||||
ArrayList<String> selectionArgs = new ArrayList<>(4);
|
||||
selectionArgs.add(account.getUuid());
|
||||
String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?";
|
||||
if (name != null){
|
||||
selectionArgs.add(name);
|
||||
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?";
|
||||
}
|
||||
if (fingerprint != null){
|
||||
selectionArgs.add(fingerprint);
|
||||
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?";
|
||||
}
|
||||
if (own != null){
|
||||
selectionArgs.add(own?"1":"0");
|
||||
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.OWN + " = ?";
|
||||
}
|
||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||
columns,
|
||||
selectionString,
|
||||
selectionArgs.toArray(new String[selectionArgs.size()]),
|
||||
null, null, null);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public IdentityKeyPair loadOwnIdentityKeyPair(Account account, String name) {
|
||||
IdentityKeyPair identityKeyPair = null;
|
||||
Cursor cursor = getIdentityKeyCursor(account, name, true);
|
||||
if(cursor.getCount() != 0) {
|
||||
cursor.moveToFirst();
|
||||
try {
|
||||
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return identityKeyPair;
|
||||
}
|
||||
|
||||
public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
|
||||
return loadIdentityKeys(account, name, null);
|
||||
}
|
||||
|
||||
public Set<IdentityKey> loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) {
|
||||
Set<IdentityKey> identityKeys = new HashSet<>();
|
||||
Cursor cursor = getIdentityKeyCursor(account, name, false);
|
||||
|
||||
while(cursor.moveToNext()) {
|
||||
if ( trust != null &&
|
||||
cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED))
|
||||
!= trust.ordinal()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return identityKeys;
|
||||
}
|
||||
|
||||
public long numTrustedKeys(Account account, String name) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
String[] args = {
|
||||
account.getUuid(),
|
||||
name
|
||||
};
|
||||
return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"
|
||||
+ " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
|
||||
storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED);
|
||||
}
|
||||
|
||||
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust trusted) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.NAME, name);
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0);
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint);
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized);
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.ordinal());
|
||||
db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
|
||||
}
|
||||
|
||||
public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
|
||||
Cursor cursor = getIdentityKeyCursor(account, fingerprint);
|
||||
AxolotlService.SQLiteAxolotlStore.Trust trust = null;
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED));
|
||||
trust = AxolotlService.SQLiteAxolotlStore.Trust.values()[trustValue];
|
||||
}
|
||||
cursor.close();
|
||||
return trust;
|
||||
}
|
||||
|
||||
public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] selectionArgs = {
|
||||
account.getUuid(),
|
||||
fingerprint
|
||||
};
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal());
|
||||
int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ",
|
||||
selectionArgs);
|
||||
return rows == 1;
|
||||
}
|
||||
|
||||
public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
|
||||
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
|
||||
}
|
||||
|
||||
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) {
|
||||
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED);
|
||||
}
|
||||
|
||||
public void recreateAxolotlDb() {
|
||||
recreateAxolotlDb(getWritableDatabase());
|
||||
}
|
||||
|
||||
public void recreateAxolotlDb(SQLiteDatabase db) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<");
|
||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME);
|
||||
db.execSQL(CREATE_SESSIONS_STATEMENT);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME);
|
||||
db.execSQL(CREATE_PREKEYS_STATEMENT);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
|
||||
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME);
|
||||
db.execSQL(CREATE_IDENTITIES_STATEMENT);
|
||||
}
|
||||
|
||||
public void wipeAxolotlDb(Account account) {
|
||||
String accountName = account.getUuid();
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] deleteArgs= {
|
||||
accountName
|
||||
};
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||
deleteArgs);
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||
deleteArgs);
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||
deleteArgs);
|
||||
db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||
deleteArgs);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,19 @@
|
||||
package eu.siacs.conversations.persistance;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64OutputStream;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
@ -17,25 +31,11 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64OutputStream;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.utils.ExifHelper;
|
||||
|
@ -1,10 +1,11 @@
|
||||
package eu.siacs.conversations.services;
|
||||
|
||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||
|
||||
public class EventReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
@ -57,11 +57,11 @@ import eu.siacs.conversations.entities.Blockable;
|
||||
import eu.siacs.conversations.entities.Bookmark;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.generator.IqGenerator;
|
||||
import eu.siacs.conversations.generator.MessageGenerator;
|
||||
import eu.siacs.conversations.generator.PresenceGenerator;
|
||||
@ -85,6 +85,7 @@ import eu.siacs.conversations.xmpp.OnContactStatusChanged;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
|
||||
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
|
||||
import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
|
||||
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
|
||||
import eu.siacs.conversations.xmpp.OnStatusChanged;
|
||||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||
@ -273,7 +274,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
}
|
||||
}
|
||||
syncDirtyContacts(account);
|
||||
scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode());
|
||||
account.getAxolotlService().publishOwnDeviceIdIfNeeded();
|
||||
account.getAxolotlService().publishBundlesIfNeeded();
|
||||
|
||||
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
|
||||
} else if (account.getStatus() == Account.State.OFFLINE) {
|
||||
resetSendingToWaiting(account);
|
||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||
@ -304,6 +308,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
private int rosterChangedListenerCount = 0;
|
||||
private OnMucRosterUpdate mOnMucRosterUpdate = null;
|
||||
private int mucRosterChangedListenerCount = 0;
|
||||
private OnNewKeysAvailable mOnNewKeysAvailable = null;
|
||||
private int newKeysAvailableListenerCount = 0;
|
||||
private SecureRandom mRandom;
|
||||
private OpenPgpServiceConnection pgpServiceConnection;
|
||||
private PgpEngine mPgpEngine = null;
|
||||
@ -591,9 +597,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
|
||||
this.accounts = databaseBackend.getAccounts();
|
||||
|
||||
for (final Account account : this.accounts) {
|
||||
account.initAccountServices(this);
|
||||
}
|
||||
restoreFromDatabase();
|
||||
|
||||
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
|
||||
@ -699,7 +702,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
|
||||
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
||||
message.getConversation().endOtrIfNeeded();
|
||||
message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
||||
message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
|
||||
new Conversation.OnMessageFound() {
|
||||
@Override
|
||||
public void onMessageFound(Message message) {
|
||||
markMessage(message,Message.STATUS_SEND_FAILED);
|
||||
@ -753,6 +757,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Message.ENCRYPTION_AXOLOTL:
|
||||
if (message.needsUploading()) {
|
||||
if (account.httpUploadAvailable() || message.fixCounterpart()) {
|
||||
this.sendFileMessage(message);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
packet = account.getAxolotlService().fetchPacketFromCache(message);
|
||||
if (packet == null) {
|
||||
account.getAxolotlService().prepareMessage(message);
|
||||
message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
if (packet != null) {
|
||||
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
@ -780,6 +800,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
conversation.startOtrSession(message.getCounterpart().getResourcepart(), false);
|
||||
}
|
||||
break;
|
||||
case Message.ENCRYPTION_AXOLOTL:
|
||||
message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -943,6 +966,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
Log.d(Config.LOGTAG,"restoring roster");
|
||||
for(Account account : accounts) {
|
||||
databaseBackend.readRoster(account.getRoster());
|
||||
account.initAccountServices(XmppConnectionService.this);
|
||||
}
|
||||
getBitmapCache().evictAll();
|
||||
Looper.prepare();
|
||||
@ -1342,6 +1366,30 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnNewKeysAvailableListener(final OnNewKeysAvailable listener) {
|
||||
synchronized (this) {
|
||||
if (checkListeners()) {
|
||||
switchToForeground();
|
||||
}
|
||||
this.mOnNewKeysAvailable = listener;
|
||||
if (this.newKeysAvailableListenerCount < 2) {
|
||||
this.newKeysAvailableListenerCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeOnNewKeysAvailableListener() {
|
||||
synchronized (this) {
|
||||
this.newKeysAvailableListenerCount--;
|
||||
if (this.newKeysAvailableListenerCount <= 0) {
|
||||
this.newKeysAvailableListenerCount = 0;
|
||||
this.mOnNewKeysAvailable = null;
|
||||
if (checkListeners()) {
|
||||
switchToBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
|
||||
synchronized (this) {
|
||||
if (checkListeners()) {
|
||||
@ -1372,7 +1420,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
&& this.mOnConversationUpdate == null
|
||||
&& this.mOnRosterUpdate == null
|
||||
&& this.mOnUpdateBlocklist == null
|
||||
&& this.mOnShowErrorToast == null);
|
||||
&& this.mOnShowErrorToast == null
|
||||
&& this.mOnNewKeysAvailable == null);
|
||||
}
|
||||
|
||||
private void switchToForeground() {
|
||||
@ -1784,7 +1833,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
account.getJid().toBareJid() + " otr session established with "
|
||||
+ conversation.getJid() + "/"
|
||||
+ otrSession.getSessionID().getUserID());
|
||||
conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
||||
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
|
||||
|
||||
@Override
|
||||
public void onMessageFound(Message message) {
|
||||
@ -2260,6 +2309,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||
}
|
||||
}
|
||||
|
||||
public void newKeysAvailable() {
|
||||
if(mOnNewKeysAvailable != null) {
|
||||
mOnNewKeysAvailable.onNewKeysAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
public Account findAccountByJid(final Jid accountJid) {
|
||||
for (Account account : this.accounts) {
|
||||
if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {
|
||||
|
@ -2,7 +2,6 @@ package eu.siacs.conversations.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
|
@ -4,7 +4,6 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
|
@ -13,11 +13,11 @@ import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
|
@ -27,7 +27,6 @@ import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.PgpEngine;
|
||||
@ -38,8 +37,8 @@ import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.MucOptions.User;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed {
|
||||
|
@ -29,6 +29,7 @@ import android.widget.QuickContactBadge;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -362,13 +363,13 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
||||
TextView key = (TextView) view.findViewById(R.id.key);
|
||||
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||
ImageButton remove = (ImageButton) view
|
||||
ImageButton removeButton = (ImageButton) view
|
||||
.findViewById(R.id.button_remove);
|
||||
remove.setVisibility(View.VISIBLE);
|
||||
removeButton.setVisibility(View.VISIBLE);
|
||||
keyType.setText("OTR Fingerprint");
|
||||
key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
|
||||
keys.addView(view);
|
||||
remove.setOnClickListener(new OnClickListener() {
|
||||
removeButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -376,6 +377,11 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||
}
|
||||
});
|
||||
}
|
||||
for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
|
||||
contact.getAccount(), contact.getJid().toBareJid().toString())) {
|
||||
hasKeys = true;
|
||||
addFingerprintRow(keys, contact.getAccount(), identityKey);
|
||||
}
|
||||
if (contact.getPgpKeyId() != 0) {
|
||||
hasKeys = true;
|
||||
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
||||
|
@ -16,6 +16,7 @@ import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.widget.SlidingPaneLayout;
|
||||
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -28,13 +29,15 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import de.timroes.android.listview.EnhancedListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import de.timroes.android.listview.EnhancedListView;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Blockable;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
@ -62,11 +65,14 @@ public class ConversationActivity extends XmppActivity
|
||||
public static final int REQUEST_SEND_MESSAGE = 0x0201;
|
||||
public static final int REQUEST_DECRYPT_PGP = 0x0202;
|
||||
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
|
||||
public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208;
|
||||
public static final int REQUEST_TRUST_KEYS_MENU = 0x0209;
|
||||
public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
|
||||
public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
|
||||
public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
|
||||
public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
|
||||
public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
|
||||
public static final int ATTACHMENT_CHOICE_INVALID = 0x0306;
|
||||
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
|
||||
private static final String STATE_PANEL_OPEN = "state_panel_open";
|
||||
private static final String STATE_PENDING_URI = "state_pending_uri";
|
||||
@ -76,6 +82,7 @@ public class ConversationActivity extends XmppActivity
|
||||
final private List<Uri> mPendingImageUris = new ArrayList<>();
|
||||
final private List<Uri> mPendingFileUris = new ArrayList<>();
|
||||
private Uri mPendingGeoUri = null;
|
||||
private boolean forbidProcessingPendings = false;
|
||||
|
||||
private View mContentView;
|
||||
|
||||
@ -398,7 +405,7 @@ public class ConversationActivity extends XmppActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
|
||||
protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
|
||||
final Conversation conversation = getSelectedConversation();
|
||||
final Account account = conversation.getAccount();
|
||||
final OnPresenceSelected callback = new OnPresenceSelected() {
|
||||
@ -534,7 +541,9 @@ public class ConversationActivity extends XmppActivity
|
||||
showInstallPgpDialog();
|
||||
}
|
||||
} else {
|
||||
selectPresenceToAttachFile(attachmentChoice,encryption);
|
||||
if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) {
|
||||
selectPresenceToAttachFile(attachmentChoice, encryption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,6 +758,12 @@ public class ConversationActivity extends XmppActivity
|
||||
showInstallPgpDialog();
|
||||
}
|
||||
break;
|
||||
case R.id.encryption_choice_axolotl:
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
|
||||
+ "Enabled axolotl for Contact " + conversation.getContact().getJid());
|
||||
conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
|
||||
item.setChecked(true);
|
||||
break;
|
||||
default:
|
||||
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
break;
|
||||
@ -763,13 +778,18 @@ public class ConversationActivity extends XmppActivity
|
||||
MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr);
|
||||
MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none);
|
||||
MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp);
|
||||
MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl);
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
otr.setEnabled(false);
|
||||
axolotl.setEnabled(false);
|
||||
} else {
|
||||
if (forceEncryption()) {
|
||||
none.setVisible(false);
|
||||
}
|
||||
}
|
||||
if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
|
||||
axolotl.setEnabled(false);
|
||||
}
|
||||
switch (conversation.getNextEncryption(forceEncryption())) {
|
||||
case Message.ENCRYPTION_NONE:
|
||||
none.setChecked(true);
|
||||
@ -780,6 +800,10 @@ public class ConversationActivity extends XmppActivity
|
||||
case Message.ENCRYPTION_PGP:
|
||||
pgp.setChecked(true);
|
||||
break;
|
||||
case Message.ENCRYPTION_AXOLOTL:
|
||||
popup.getMenu().findItem(R.id.encryption_choice_axolotl)
|
||||
.setChecked(true);
|
||||
break;
|
||||
default:
|
||||
none.setChecked(true);
|
||||
break;
|
||||
@ -944,18 +968,23 @@ public class ConversationActivity extends XmppActivity
|
||||
this.mConversationFragment.reInit(getSelectedConversation());
|
||||
}
|
||||
|
||||
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
|
||||
attachImageToConversation(getSelectedConversation(),i.next());
|
||||
if(!forbidProcessingPendings) {
|
||||
for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
|
||||
Uri foo = i.next();
|
||||
attachImageToConversation(getSelectedConversation(), foo);
|
||||
}
|
||||
|
||||
for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
|
||||
attachFileToConversation(getSelectedConversation(),i.next());
|
||||
for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
|
||||
attachFileToConversation(getSelectedConversation(), i.next());
|
||||
}
|
||||
|
||||
if (mPendingGeoUri != null) {
|
||||
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
|
||||
mPendingGeoUri = null;
|
||||
}
|
||||
}
|
||||
forbidProcessingPendings = false;
|
||||
|
||||
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
||||
setIntent(new Intent());
|
||||
}
|
||||
@ -1065,6 +1094,9 @@ public class ConversationActivity extends XmppActivity
|
||||
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
|
||||
this.mPendingGeoUri = null;
|
||||
}
|
||||
} else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) {
|
||||
this.forbidProcessingPendings = !xmppConnectionServiceBound;
|
||||
mConversationFragment.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
} else {
|
||||
mPendingImageUris.clear();
|
||||
@ -1217,6 +1249,26 @@ public class ConversationActivity extends XmppActivity
|
||||
return getPreferences().getBoolean("indicate_received", false);
|
||||
}
|
||||
|
||||
protected boolean trustKeysIfNeeded(int requestCode) {
|
||||
return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
|
||||
}
|
||||
|
||||
protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
|
||||
AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
|
||||
if(!axolotlService.getPendingKeys(mSelectedConversation.getContact()).isEmpty()
|
||||
|| !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty()) {
|
||||
axolotlService.createSessionsIfNeeded(mSelectedConversation, false);
|
||||
Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
|
||||
intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString());
|
||||
intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString());
|
||||
intent.putExtra("choice", attachmentChoice);
|
||||
startActivityForResult(intent, requestCode);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshUiReal() {
|
||||
updateConversationList();
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.PendingIntent;
|
||||
@ -46,12 +47,12 @@ import eu.siacs.conversations.crypto.PgpEngine;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.Presences;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
|
||||
import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
|
||||
@ -303,6 +304,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||
sendOtrMessage(message);
|
||||
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||
sendPgpMessage(message);
|
||||
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) {
|
||||
if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
|
||||
sendAxolotlMessage(message);
|
||||
}
|
||||
} else {
|
||||
sendPlainTextMessage(message);
|
||||
}
|
||||
@ -323,6 +328,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||
case Message.ENCRYPTION_OTR:
|
||||
mEditMessage.setHint(getString(R.string.send_otr_message));
|
||||
break;
|
||||
case Message.ENCRYPTION_AXOLOTL:
|
||||
mEditMessage.setHint(getString(R.string.send_axolotl_message));
|
||||
break;
|
||||
case Message.ENCRYPTION_PGP:
|
||||
mEditMessage.setHint(getString(R.string.send_pgp_message));
|
||||
break;
|
||||
@ -1120,6 +1128,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected void sendAxolotlMessage(final Message message) {
|
||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
}
|
||||
|
||||
protected void sendOtrMessage(final Message message) {
|
||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||
@ -1182,4 +1197,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||
updateSendButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode,
|
||||
final Intent data) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
|
||||
final String body = mEditMessage.getText().toString();
|
||||
Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
|
||||
sendAxolotlMessage(message);
|
||||
} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
|
||||
int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
|
||||
activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption(activity.forceEncryption()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
@ -23,6 +25,10 @@ import android.widget.TableLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||
@ -54,9 +60,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
private TextView mServerInfoPep;
|
||||
private TextView mSessionEst;
|
||||
private TextView mOtrFingerprint;
|
||||
private TextView mAxolotlFingerprint;
|
||||
private ImageView mAvatar;
|
||||
private RelativeLayout mOtrFingerprintBox;
|
||||
private RelativeLayout mAxolotlFingerprintBox;
|
||||
private ImageButton mOtrFingerprintToClipboardButton;
|
||||
private ImageButton mAxolotlFingerprintToClipboardButton;
|
||||
private ImageButton mRegenerateAxolotlKeyButton;
|
||||
private LinearLayout keys;
|
||||
private LinearLayout keysCard;
|
||||
|
||||
private Jid jidToEdit;
|
||||
private Account mAccount;
|
||||
@ -310,6 +322,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
|
||||
this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
|
||||
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
||||
this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint);
|
||||
this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box);
|
||||
this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard);
|
||||
this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key);
|
||||
this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
|
||||
this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
|
||||
this.mSaveButton = (Button) findViewById(R.id.save_button);
|
||||
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
|
||||
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
|
||||
@ -338,6 +356,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
|
||||
final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
|
||||
final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
|
||||
final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
|
||||
if (mAccount != null && mAccount.isOnlineAndConnected()) {
|
||||
if (!mAccount.getXmppConnection().getFeatures().blocking()) {
|
||||
showBlocklist.setVisible(false);
|
||||
@ -345,6 +364,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
if (!mAccount.getXmppConnection().getFeatures().register()) {
|
||||
changePassword.setVisible(false);
|
||||
}
|
||||
Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
|
||||
if (otherDevices == null || otherDevices.isEmpty()) {
|
||||
clearDevices.setVisible(false);
|
||||
}
|
||||
} else {
|
||||
showQrCode.setVisible(false);
|
||||
showBlocklist.setVisible(false);
|
||||
@ -415,6 +438,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
changePasswordIntent.putExtra("account", mAccount.getJid().toString());
|
||||
startActivity(changePasswordIntent);
|
||||
break;
|
||||
case R.id.action_clear_devices:
|
||||
showWipePepDialog();
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@ -477,10 +503,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
} else {
|
||||
this.mServerInfoPep.setText(R.string.server_info_unavailable);
|
||||
}
|
||||
final String fingerprint = this.mAccount.getOtrFingerprint();
|
||||
if (fingerprint != null) {
|
||||
final String otrFingerprint = this.mAccount.getOtrFingerprint();
|
||||
if (otrFingerprint != null) {
|
||||
this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
|
||||
this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint));
|
||||
this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
|
||||
this.mOtrFingerprintToClipboardButton
|
||||
.setVisibility(View.VISIBLE);
|
||||
this.mOtrFingerprintToClipboardButton
|
||||
@ -489,7 +515,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
|
||||
if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) {
|
||||
if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
|
||||
Toast.makeText(
|
||||
EditAccountActivity.this,
|
||||
R.string.toast_message_otr_fingerprint,
|
||||
@ -500,6 +526,55 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
} else {
|
||||
this.mOtrFingerprintBox.setVisibility(View.GONE);
|
||||
}
|
||||
final String axolotlFingerprint = this.mAccount.getAxolotlService().getOwnPublicKey().getFingerprint();
|
||||
if (axolotlFingerprint != null) {
|
||||
this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE);
|
||||
this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(axolotlFingerprint));
|
||||
this.mAxolotlFingerprintToClipboardButton
|
||||
.setVisibility(View.VISIBLE);
|
||||
this.mAxolotlFingerprintToClipboardButton
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
|
||||
if (copyTextToClipboard(axolotlFingerprint, R.string.axolotl_fingerprint)) {
|
||||
Toast.makeText(
|
||||
EditAccountActivity.this,
|
||||
R.string.toast_message_axolotl_fingerprint,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.mRegenerateAxolotlKeyButton
|
||||
.setVisibility(View.VISIBLE);
|
||||
this.mRegenerateAxolotlKeyButton
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
showRegenerateAxolotlKeyDialog();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.mAxolotlFingerprintBox.setVisibility(View.GONE);
|
||||
}
|
||||
final IdentityKey ownKey = mAccount.getAxolotlService().getOwnPublicKey();
|
||||
boolean hasKeys = false;
|
||||
keys.removeAllViews();
|
||||
for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
|
||||
mAccount, mAccount.getJid().toBareJid().toString())) {
|
||||
if(ownKey.equals(identityKey)) {
|
||||
continue;
|
||||
}
|
||||
hasKeys = true;
|
||||
addFingerprintRow(keys, mAccount, identityKey);
|
||||
}
|
||||
if (hasKeys) {
|
||||
keysCard.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
keysCard.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (this.mAccount.errorStatus()) {
|
||||
this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
|
||||
@ -512,4 +587,36 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||
this.mStats.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void showRegenerateAxolotlKeyDialog() {
|
||||
Builder builder = new Builder(this);
|
||||
builder.setTitle("Regenerate Key");
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)");
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setPositiveButton("Yes",
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mAccount.getAxolotlService().regenerateKeys();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
public void showWipePepDialog() {
|
||||
Builder builder = new Builder(this);
|
||||
builder.setTitle(getString(R.string.clear_other_devices));
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
builder.setMessage(getString(R.string.clear_other_devices_desc));
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setPositiveButton(getString(R.string.accept),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mAccount.getAxolotlService().wipeOtherPepDevices();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,20 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -7,20 +22,6 @@ import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||
import eu.siacs.conversations.ui.adapter.AccountAdapter;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate {
|
||||
|
||||
|
@ -1,19 +1,6 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.security.KeyStoreException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.duenndns.ssl.MemorizingTrustManager;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
@ -25,6 +12,17 @@ import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.security.KeyStoreException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.duenndns.ssl.MemorizingTrustManager;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
|
||||
public class SettingsActivity extends XmppActivity implements
|
||||
OnSharedPreferenceChangeListener {
|
||||
private SettingsFragment mSettingsFragment;
|
||||
|
@ -17,7 +17,6 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
|
255
src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
Normal file
@ -0,0 +1,255 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailable {
|
||||
private Jid accountJid;
|
||||
private Jid contactJid;
|
||||
private boolean hasOtherTrustedKeys = false;
|
||||
private boolean hasPendingFetches = false;
|
||||
|
||||
private Contact contact;
|
||||
private TextView ownKeysTitle;
|
||||
private LinearLayout ownKeys;
|
||||
private LinearLayout ownKeysCard;
|
||||
private TextView foreignKeysTitle;
|
||||
private LinearLayout foreignKeys;
|
||||
private LinearLayout foreignKeysCard;
|
||||
private Button mSaveButton;
|
||||
private Button mCancelButton;
|
||||
|
||||
private final Map<IdentityKey, Boolean> ownKeysToTrust = new HashMap<>();
|
||||
private final Map<IdentityKey, Boolean> foreignKeysToTrust = new HashMap<>();
|
||||
|
||||
private final OnClickListener mSaveButtonListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
commitTrusts();
|
||||
Intent data = new Intent();
|
||||
data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
private final OnClickListener mCancelButtonListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void refreshUiReal() {
|
||||
invalidateOptionsMenu();
|
||||
populateView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getShareableUri() {
|
||||
if (contact != null) {
|
||||
return contact.getShareableUri();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_trust_keys);
|
||||
try {
|
||||
this.accountJid = Jid.fromString(getIntent().getExtras().getString("account"));
|
||||
} catch (final InvalidJidException ignored) {
|
||||
}
|
||||
try {
|
||||
this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
|
||||
} catch (final InvalidJidException ignored) {
|
||||
}
|
||||
|
||||
ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
|
||||
ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
|
||||
ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
|
||||
foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title);
|
||||
foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details);
|
||||
foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card);
|
||||
mCancelButton = (Button) findViewById(R.id.cancel_button);
|
||||
mCancelButton.setOnClickListener(mCancelButtonListener);
|
||||
mSaveButton = (Button) findViewById(R.id.save_button);
|
||||
mSaveButton.setOnClickListener(mSaveButtonListener);
|
||||
|
||||
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setHomeButtonEnabled(true);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateView() {
|
||||
setTitle(getString(R.string.trust_keys));
|
||||
ownKeys.removeAllViews();
|
||||
foreignKeys.removeAllViews();
|
||||
boolean hasOwnKeys = false;
|
||||
boolean hasForeignKeys = false;
|
||||
for(final IdentityKey identityKey : ownKeysToTrust.keySet()) {
|
||||
hasOwnKeys = true;
|
||||
addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey,
|
||||
Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
|
||||
new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
ownKeysToTrust.put(identityKey, isChecked);
|
||||
refreshUi();
|
||||
xmppConnectionService.updateAccountUi();
|
||||
xmppConnectionService.updateConversationUi();
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) {
|
||||
hasForeignKeys = true;
|
||||
addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey,
|
||||
Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
|
||||
new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
foreignKeysToTrust.put(identityKey, isChecked);
|
||||
refreshUi();
|
||||
xmppConnectionService.updateAccountUi();
|
||||
xmppConnectionService.updateConversationUi();
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if(hasOwnKeys) {
|
||||
ownKeysTitle.setText(accountJid.toString());
|
||||
ownKeysCard.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if(hasForeignKeys) {
|
||||
foreignKeysTitle.setText(contactJid.toString());
|
||||
foreignKeysCard.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if(hasPendingFetches) {
|
||||
setFetching();
|
||||
lock();
|
||||
} else {
|
||||
if (!hasOtherTrustedKeys && !foreignKeysToTrust.values().contains(true)){
|
||||
lock();
|
||||
} else {
|
||||
unlock();
|
||||
}
|
||||
setDone();
|
||||
}
|
||||
}
|
||||
|
||||
private void getFingerprints(final Account account) {
|
||||
Set<IdentityKey> ownKeysSet = account.getAxolotlService().getPendingKeys();
|
||||
for(final IdentityKey identityKey : ownKeysSet) {
|
||||
if(!ownKeysToTrust.containsKey(identityKey)) {
|
||||
ownKeysToTrust.put(identityKey, false);
|
||||
}
|
||||
}
|
||||
Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getPendingKeys(contact);
|
||||
for(final IdentityKey identityKey : foreignKeysSet) {
|
||||
if(!foreignKeysToTrust.containsKey(identityKey)) {
|
||||
foreignKeysToTrust.put(identityKey, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackendConnected() {
|
||||
if ((accountJid != null) && (contactJid != null)) {
|
||||
final Account account = xmppConnectionService
|
||||
.findAccountByJid(accountJid);
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
this.contact = account.getRoster().getContact(contactJid);
|
||||
ownKeysToTrust.clear();
|
||||
foreignKeysToTrust.clear();
|
||||
getFingerprints(account);
|
||||
|
||||
if(account.getAxolotlService().getNumTrustedKeys(contact) > 0) {
|
||||
hasOtherTrustedKeys = true;
|
||||
}
|
||||
Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, false);
|
||||
if(account.getAxolotlService().hasPendingKeyFetches(conversation)) {
|
||||
hasPendingFetches = true;
|
||||
}
|
||||
|
||||
populateView();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewKeysAvailable() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Account account = xmppConnectionService
|
||||
.findAccountByJid(accountJid);
|
||||
hasPendingFetches = false;
|
||||
getFingerprints(account);
|
||||
refreshUi();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void commitTrusts() {
|
||||
for(IdentityKey identityKey:ownKeysToTrust.keySet()) {
|
||||
contact.getAccount().getAxolotlService().setFingerprintTrust(
|
||||
identityKey.getFingerprint().replaceAll("\\s", ""),
|
||||
Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
|
||||
}
|
||||
for(IdentityKey identityKey:foreignKeysToTrust.keySet()) {
|
||||
contact.getAccount().getAxolotlService().setFingerprintTrust(
|
||||
identityKey.getFingerprint().replaceAll("\\s", ""),
|
||||
Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
|
||||
}
|
||||
}
|
||||
|
||||
private void unlock() {
|
||||
mSaveButton.setEnabled(true);
|
||||
mSaveButton.setTextColor(getPrimaryTextColor());
|
||||
}
|
||||
|
||||
private void lock() {
|
||||
mSaveButton.setEnabled(false);
|
||||
mSaveButton.setTextColor(getSecondaryTextColor());
|
||||
}
|
||||
|
||||
private void setDone() {
|
||||
mSaveButton.setText(getString(R.string.done));
|
||||
}
|
||||
|
||||
private void setFetching() {
|
||||
mSaveButton.setText(getString(R.string.fetching_keys));
|
||||
}
|
||||
}
|
@ -43,8 +43,11 @@ import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
@ -56,6 +59,8 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
|
||||
import net.java.otr4j.session.SessionID;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
@ -65,6 +70,7 @@ import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
@ -74,7 +80,10 @@ import eu.siacs.conversations.entities.Presences;
|
||||
import eu.siacs.conversations.services.AvatarService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
||||
import eu.siacs.conversations.ui.widget.Switch;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||
import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
|
||||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
@ -287,6 +296,9 @@ public abstract class XmppActivity extends Activity {
|
||||
if (this instanceof XmppConnectionService.OnShowErrorToast) {
|
||||
this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
|
||||
}
|
||||
if (this instanceof OnNewKeysAvailable) {
|
||||
this.xmppConnectionService.setOnNewKeysAvailableListener((OnNewKeysAvailable) this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void unregisterListeners() {
|
||||
@ -308,6 +320,9 @@ public abstract class XmppActivity extends Activity {
|
||||
if (this instanceof XmppConnectionService.OnShowErrorToast) {
|
||||
this.xmppConnectionService.removeOnShowErrorToastListener();
|
||||
}
|
||||
if (this instanceof OnNewKeysAvailable) {
|
||||
this.xmppConnectionService.removeOnNewKeysAvailableListener();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -588,6 +603,76 @@ public abstract class XmppActivity extends Activity {
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected void addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) {
|
||||
final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
|
||||
final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService()
|
||||
.getFingerprintTrust(fingerprint);
|
||||
addFingerprintRowWithListeners(keys, account, identityKey, trust, true,
|
||||
new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) {
|
||||
account.getAxolotlService().setFingerprintTrust(fingerprint,
|
||||
(isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED :
|
||||
AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
|
||||
}
|
||||
refreshUi();
|
||||
xmppConnectionService.updateAccountUi();
|
||||
xmppConnectionService.updateConversationUi();
|
||||
}
|
||||
},
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
account.getAxolotlService().setFingerprintTrust(fingerprint,
|
||||
AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
|
||||
refreshUi();
|
||||
xmppConnectionService.updateAccountUi();
|
||||
xmppConnectionService.updateConversationUi();
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account,
|
||||
IdentityKey identityKey,
|
||||
AxolotlService.SQLiteAxolotlStore.Trust trust,
|
||||
boolean showTag,
|
||||
CompoundButton.OnCheckedChangeListener
|
||||
onCheckedChangeListener,
|
||||
View.OnClickListener onClickListener) {
|
||||
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
|
||||
TextView key = (TextView) view.findViewById(R.id.key);
|
||||
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||
Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
|
||||
trustToggle.setVisibility(View.VISIBLE);
|
||||
trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
|
||||
trustToggle.setOnClickListener(onClickListener);
|
||||
|
||||
switch (trust) {
|
||||
case UNTRUSTED:
|
||||
case TRUSTED:
|
||||
trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false);
|
||||
trustToggle.setEnabled(true);
|
||||
break;
|
||||
case UNDECIDED:
|
||||
trustToggle.setChecked(false, false);
|
||||
trustToggle.setEnabled(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (showTag) {
|
||||
keyType.setText(getString(R.string.axolotl_fingerprint));
|
||||
} else {
|
||||
keyType.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
|
||||
keys.addView(view);
|
||||
|
||||
}
|
||||
|
||||
public void selectPresence(final Conversation conversation,
|
||||
final OnPresenceSelected listener) {
|
||||
final Contact contact = conversation.getContact();
|
||||
|
@ -1,13 +1,5 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.ui.ManageAccountActivity;
|
||||
import eu.siacs.conversations.ui.widget.Switch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -17,6 +9,14 @@ import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.ui.ManageAccountActivity;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.ui.widget.Switch;
|
||||
|
||||
public class AccountAdapter extends ArrayAdapter<Account> {
|
||||
|
||||
private XmppActivity activity;
|
||||
|
@ -21,8 +21,8 @@ import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.ui.ConversationActivity;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
|
@ -1,13 +1,13 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class KnownHostsAdapter extends ArrayAdapter<String> {
|
||||
private ArrayList<String> domains;
|
||||
private Filter domainFilter = new Filter() {
|
||||
|
@ -1,15 +1,5 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
@ -26,6 +16,16 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
||||
|
||||
protected XmppActivity activity;
|
||||
|
@ -3,6 +3,7 @@ package eu.siacs.conversations.ui.adapter;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.text.Spannable;
|
||||
@ -26,13 +27,14 @@ import android.widget.Toast;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Message.FileParams;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.ui.ConversationActivity;
|
||||
import eu.siacs.conversations.utils.GeoHelper;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
@ -154,6 +156,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||
viewHolder.indicator.setVisibility(View.GONE);
|
||||
} else {
|
||||
viewHolder.indicator.setVisibility(View.VISIBLE);
|
||||
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
|
||||
AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation()
|
||||
.getAccount().getAxolotlService().getFingerprintTrust(
|
||||
message.getAxolotlFingerprint());
|
||||
|
||||
if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) {
|
||||
viewHolder.indicator.setColorFilter(Color.RED);
|
||||
} else {
|
||||
viewHolder.indicator.clearColorFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
|
||||
|
@ -7,8 +7,6 @@ import android.view.ViewConfiguration;
|
||||
|
||||
import com.kyleduo.switchbutton.SwitchButton;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
public class Switch extends SwitchButton {
|
||||
|
||||
private int mTouchSlop;
|
||||
|
@ -96,11 +96,10 @@ public final class CryptoHelper {
|
||||
} else if (fingerprint.length() < 40) {
|
||||
return fingerprint;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(fingerprint);
|
||||
builder.insert(8, " ");
|
||||
builder.insert(17, " ");
|
||||
builder.insert(26, " ");
|
||||
builder.insert(35, " ");
|
||||
StringBuilder builder = new StringBuilder(fingerprint.replaceAll("\\s",""));
|
||||
for(int i=8;i<builder.length();i+=9) {
|
||||
builder.insert(i, ' ');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,7 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import de.measite.minidns.Client;
|
||||
import de.measite.minidns.DNSMessage;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.Record.TYPE;
|
||||
import de.measite.minidns.Record.CLASS;
|
||||
import de.measite.minidns.record.SRV;
|
||||
import de.measite.minidns.record.A;
|
||||
import de.measite.minidns.record.AAAA;
|
||||
import de.measite.minidns.record.Data;
|
||||
import de.measite.minidns.util.NameUtil;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
@ -22,8 +12,18 @@ import java.util.Random;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import de.measite.minidns.Client;
|
||||
import de.measite.minidns.DNSMessage;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.Record.CLASS;
|
||||
import de.measite.minidns.Record.TYPE;
|
||||
import de.measite.minidns.record.A;
|
||||
import de.measite.minidns.record.AAAA;
|
||||
import de.measite.minidns.record.Data;
|
||||
import de.measite.minidns.record.SRV;
|
||||
import de.measite.minidns.util.NameUtil;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
public class DNSHelper {
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@ -8,8 +10,6 @@ import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class ExceptionHandler implements UncaughtExceptionHandler {
|
||||
|
||||
private UncaughtExceptionHandler defaultHandler;
|
||||
|
@ -1,5 +1,17 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@ -15,18 +27,6 @@ import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public class ExceptionHelper {
|
||||
public static void init(Context context) {
|
||||
if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OnPhoneContactsLoadedListener {
|
||||
public void onPhoneContactsLoaded(List<Bundle> phoneContacts);
|
||||
}
|
||||
|
@ -1,9 +1,5 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.CursorLoader;
|
||||
import android.content.Loader;
|
||||
@ -15,6 +11,9 @@ import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
public class PhoneHelper {
|
||||
|
||||
public static void loadPhoneContacts(Context context,final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) {
|
||||
|
@ -1,5 +1,10 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
@ -9,15 +14,10 @@ import java.util.Locale;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
public class UIHelper {
|
||||
|
||||
private static String BLACK_HEART_SUIT = "\u2665";
|
||||
|
@ -21,6 +21,11 @@ public class Element {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Element(String name, String xmlns) {
|
||||
this.name = name;
|
||||
this.setAttribute("xmlns", xmlns);
|
||||
}
|
||||
|
||||
public Element addChild(Element child) {
|
||||
this.content = null;
|
||||
children.add(child);
|
||||
|
@ -1,19 +1,19 @@
|
||||
package eu.siacs.conversations.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
public class XmlReader {
|
||||
private XmlPullParser parser;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
|
@ -0,0 +1,5 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
public interface OnNewKeysAvailable {
|
||||
public void onNewKeysAvailable();
|
||||
}
|
@ -26,7 +26,6 @@ import java.net.IDN;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -1,5 +1,10 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
@ -8,17 +13,13 @@ import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
|
@ -1,16 +1,18 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.Xmlns;
|
||||
|
@ -1,5 +1,8 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -7,9 +10,6 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
|
@ -1,10 +1,10 @@
|
||||
package eu.siacs.conversations.xmpp.pep;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
public class Avatar {
|
||||
|
||||
public enum Origin { PEP, VCARD };
|
||||
|
@ -2,8 +2,6 @@ package eu.siacs.conversations.xmpp.stanzas;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import eu.siacs.conversations.parser.AbstractParser;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
@ -29,6 +27,11 @@ public class MessagePacket extends AbstractStanza {
|
||||
this.children.add(0, body);
|
||||
}
|
||||
|
||||
public void setAxolotlMessage(Element axolotlMessage) {
|
||||
this.children.remove(findChild("body"));
|
||||
this.children.add(0, axolotlMessage);
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
switch (type) {
|
||||
case TYPE_CHAT:
|
||||
|
BIN
src/main/res/drawable-hdpi/ic_action_done.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/res/drawable-hdpi/ic_done_black_24dp.png
Normal file
After Width: | Height: | Size: 177 B |
BIN
src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png
Normal file
After Width: | Height: | Size: 508 B |
BIN
src/main/res/drawable-mdpi/ic_action_done.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/res/drawable-mdpi/ic_done_black_24dp.png
Normal file
After Width: | Height: | Size: 130 B |
BIN
src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png
Normal file
After Width: | Height: | Size: 356 B |
BIN
src/main/res/drawable-xhdpi/ic_action_done.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/main/res/drawable-xhdpi/ic_done_black_24dp.png
Normal file
After Width: | Height: | Size: 188 B |
BIN
src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png
Normal file
After Width: | Height: | Size: 644 B |
BIN
src/main/res/drawable-xxhdpi/ic_done_black_24dp.png
Normal file
After Width: | Height: | Size: 227 B |
BIN
src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png
Normal file
After Width: | Height: | Size: 882 B |
BIN
src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png
Normal file
After Width: | Height: | Size: 277 B |
BIN
src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
@ -342,6 +342,95 @@
|
||||
android:visibility="visible"
|
||||
android:contentDescription="@string/copy_otr_clipboard_description"/>
|
||||
</RelativeLayout>
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/axolotl_fingerprint_box"
|
||||
android:layout_marginTop="32dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@+id/axolotl_actions"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/axolotl_fingerprint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black87"
|
||||
android:textSize="?attr/TextSizeBody"
|
||||
android:typeface="monospace" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black54"
|
||||
android:textSize="?attr/TextSizeInfo"
|
||||
android:text="@string/this_device_axolotl_fingerprint"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/axolotl_actions"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_copy_axolotl_to_clipboard"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:padding="@dimen/image_button_padding"
|
||||
android:src="?attr/icon_copy"
|
||||
android:visibility="visible"
|
||||
android:contentDescription="@string/copy_axolotl_clipboard_description"/>
|
||||
<ImageButton
|
||||
android:id="@+id/action_regenerate_axolotl_key"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:padding="@dimen/image_button_padding"
|
||||
android:src="?attr/icon_refresh"
|
||||
android:visibility="visible"
|
||||
android:contentDescription="@string/regenerate_axolotl_key"/>
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/other_device_keys_card"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin"
|
||||
android:background="@drawable/infocard_border"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/infocard_padding"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/other_device_keys_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black87"
|
||||
android:textSize="?attr/TextSizeHeadline"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/other_devices"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/other_device_keys"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="?android:dividerHorizontal"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle" >
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
120
src/main/res/layout/activity_trust_keys.xml
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/grey200" >
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/button_bar"
|
||||
android:layout_alignParentTop="true" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/own_keys_card"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin"
|
||||
android:background="@drawable/infocard_border"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/infocard_padding"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/own_keys_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black87"
|
||||
android:textSize="?attr/TextSizeHeadline"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/own_keys_details"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="?android:dividerHorizontal"
|
||||
android:showDividers="middle"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/foreign_keys_card"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin"
|
||||
android:background="@drawable/infocard_border"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/infocard_padding"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/foreign_keys_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black87"
|
||||
android:textSize="?attr/TextSizeHeadline"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/foreign_keys_details"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="?android:dividerHorizontal"
|
||||
android:showDividers="middle"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
<LinearLayout
|
||||
android:id="@+id/button_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/cancel"
|
||||
android:textColor="@color/black87" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginBottom="7dp"
|
||||
android:layout_marginTop="7dp"
|
||||
android:background="@color/black12" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/save_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:enabled="true"
|
||||
android:textColor="@color/black54"
|
||||
android:text="@string/done"/>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
@ -3,12 +3,11 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<LinearLayout
|
||||
<RelativeLayout
|
||||
android:id="@+id/key_data"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@+id/button_remove"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp" >
|
||||
|
||||
<TextView
|
||||
@ -16,6 +15,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black87"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@+id/tgl_trust"
|
||||
android:textSize="?attr/TextSizeBody"
|
||||
android:typeface="monospace" />
|
||||
|
||||
@ -24,18 +25,41 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black54"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/key"
|
||||
android:textSize="?attr/TextSizeInfo"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/key_trust"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/key"
|
||||
android:visibility="gone"
|
||||
android:textColor="@color/black54"
|
||||
android:textSize="?attr/TextSizeInfo"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_remove"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_toRightOf="@+id/key"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:padding="@dimen/image_button_padding"
|
||||
android:src="?attr/icon_remove"
|
||||
android:visibility="invisible" />
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
<eu.siacs.conversations.ui.widget.Switch
|
||||
android:id="@+id/tgl_trust"
|
||||
android:visibility="invisible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
style="@style/MD"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
@ -20,4 +20,8 @@
|
||||
<item android:id="@+id/action_change_password_on_server"
|
||||
android:title="@string/change_password"
|
||||
android:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/action_clear_devices"
|
||||
android:title="@string/clear_other_devices"
|
||||
android:showAsAction="never"/>
|
||||
</menu>
|
@ -11,6 +11,9 @@
|
||||
<item
|
||||
android:id="@+id/encryption_choice_pgp"
|
||||
android:title="@string/encryption_choice_pgp"/>
|
||||
<item
|
||||
android:id="@+id/encryption_choice_axolotl"
|
||||
android:title="@string/encryption_choice_axolotl"/>
|
||||
</group>
|
||||
|
||||
</menu>
|
@ -21,8 +21,10 @@
|
||||
<item name="attr/icon_download">@drawable/ic_file_download_white_24dp</item>
|
||||
<item name="attr/icon_edit">@drawable/ic_edit_white_24dp</item>
|
||||
<item name="attr/icon_edit_dark">@drawable/ic_edit_grey600_24dp</item>
|
||||
<item name="attr/icon_done">@drawable/ic_done_black_24dp</item>
|
||||
<item name="attr/icon_group">@drawable/ic_group_white_24dp</item>
|
||||
<item name="attr/icon_new">@drawable/ic_add_white_24dp</item>
|
||||
<item name="attr/icon_refresh">@drawable/ic_refresh_grey600_24dp</item>
|
||||
<item name="attr/icon_new_attachment">@drawable/ic_attach_file_white_24dp</item>
|
||||
<item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item>
|
||||
<item name="attr/icon_remove">@drawable/ic_delete_grey600_24dp</item>
|
||||
|
@ -14,6 +14,7 @@
|
||||
<attr name="icon_download" format="reference"/>
|
||||
<attr name="icon_edit" format="reference"/>
|
||||
<attr name="icon_edit_dark" format="reference"/>
|
||||
<attr name="icon_done" format="reference"/>
|
||||
<attr name="icon_group" format="reference"/>
|
||||
<attr name="icon_new" format="reference"/>
|
||||
<attr name="icon_new_attachment" format="reference"/>
|
||||
|
@ -80,6 +80,7 @@
|
||||
<string name="choose_presence">Choose presence to contact</string>
|
||||
<string name="send_plain_text_message">Send plain text message</string>
|
||||
<string name="send_otr_message">Send OTR encrypted message</string>
|
||||
<string name="send_axolotl_message">Send Axolotl encrypted message</string>
|
||||
<string name="send_pgp_message">Send OpenPGP encrypted message</string>
|
||||
<string name="your_nick_has_been_changed">Your nickname has been changed</string>
|
||||
<string name="send_unencrypted">Send unencrypted</string>
|
||||
@ -154,6 +155,7 @@
|
||||
<string name="encryption_choice_none">Plain text</string>
|
||||
<string name="encryption_choice_otr">OTR</string>
|
||||
<string name="encryption_choice_pgp">OpenPGP</string>
|
||||
<string name="encryption_choice_axolotl">Axolotl</string>
|
||||
<string name="mgmt_account_edit">Edit account</string>
|
||||
<string name="mgmt_account_delete">Delete account</string>
|
||||
<string name="mgmt_account_disable">Temporarily disable</string>
|
||||
@ -206,6 +208,13 @@
|
||||
<string name="reception_failed">Reception failed</string>
|
||||
<string name="your_fingerprint">Your fingerprint</string>
|
||||
<string name="otr_fingerprint">OTR fingerprint</string>
|
||||
<string name="axolotl_fingerprint">Axolotl fingerprint</string>
|
||||
<string name="this_device_axolotl_fingerprint">Own Axolotl fingerprint</string>
|
||||
<string name="other_devices">Other devices</string>
|
||||
<string name="trust_keys">Trust Axolotl Keys</string>
|
||||
<string name="fetching_keys">Fetching keys...</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="axolotl_devicelist">Other own Axolotl Devices</string>
|
||||
<string name="verify">Verify</string>
|
||||
<string name="decrypt">Decrypt</string>
|
||||
<string name="conferences">Conferences</string>
|
||||
@ -312,6 +321,7 @@
|
||||
<string name="pref_conference_name">Conference name</string>
|
||||
<string name="pref_conference_name_summary">Use room’s subject instead of JID to identify conferences</string>
|
||||
<string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string>
|
||||
<string name="toast_message_axolotl_fingerprint">Axolotl fingerprint copied to clipboard!</string>
|
||||
<string name="conference_banned">You are banned from this conference</string>
|
||||
<string name="conference_members_only">This conference is members only</string>
|
||||
<string name="conference_kicked">You have been kicked from this conference</string>
|
||||
@ -378,6 +388,11 @@
|
||||
<string name="reset">Reset</string>
|
||||
<string name="account_image_description">Account avatar</string>
|
||||
<string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string>
|
||||
<string name="copy_axolotl_clipboard_description">Copy Axolotl fingerprint to clipboard</string>
|
||||
<string name="regenerate_axolotl_key">Copy Axolotl fingerprint to clipboard</string>
|
||||
<string name="wipe_axolotl_pep">Wipe other devices from PEP</string>
|
||||
<string name="clear_other_devices">Clear devices</string>
|
||||
<string name="clear_other_devices_desc">Are you sure you want to clear all other devices from the axolotl announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime.</string>
|
||||
<string name="fetching_history_from_server">Fetching history from server</string>
|
||||
<string name="no_more_history_on_server">No more history on server</string>
|
||||
<string name="updating">Updating…</string>
|
||||
|
@ -18,6 +18,7 @@
|
||||
<item name="attr/icon_download">@drawable/ic_action_download</item>
|
||||
<item name="attr/icon_edit">@drawable/ic_action_edit</item>
|
||||
<item name="attr/icon_edit_dark">@drawable/ic_action_edit_dark</item>
|
||||
<item name="attr/icon_done">@drawable/ic_action_done</item>
|
||||
|
||||
<item name="attr/icon_group">@drawable/ic_action_group</item>
|
||||
<item name="attr/icon_new">@drawable/ic_action_new</item>
|
||||
|