diff options
Diffstat (limited to 'src/main/java/eu')
36 files changed, 512 insertions, 278 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java index af11756f..3663cd3b 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java @@ -53,28 +53,30 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { this.mXmppConnectionService = service; } - private KeyPair loadKey(JSONObject keys) { + private KeyPair loadKey(final JSONObject keys) { if (keys == null) { return null; } - try { - BigInteger x = new BigInteger(keys.getString("otr_x"), 16); - BigInteger y = new BigInteger(keys.getString("otr_y"), 16); - BigInteger p = new BigInteger(keys.getString("otr_p"), 16); - BigInteger q = new BigInteger(keys.getString("otr_q"), 16); - BigInteger g = new BigInteger(keys.getString("otr_g"), 16); - KeyFactory keyFactory = KeyFactory.getInstance("DSA"); - DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g); - DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g); - PublicKey publicKey = keyFactory.generatePublic(pubKeySpec); - PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); - return new KeyPair(publicKey, privateKey); - } catch (JSONException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (InvalidKeySpecException e) { - return null; + synchronized (keys) { + try { + BigInteger x = new BigInteger(keys.getString("otr_x"), 16); + BigInteger y = new BigInteger(keys.getString("otr_y"), 16); + BigInteger p = new BigInteger(keys.getString("otr_p"), 16); + BigInteger q = new BigInteger(keys.getString("otr_q"), 16); + BigInteger g = new BigInteger(keys.getString("otr_g"), 16); + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g); + DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g); + PublicKey publicKey = keyFactory.generatePublic(pubKeySpec); + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + return new KeyPair(publicKey, privateKey); + } catch (JSONException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (InvalidKeySpecException e) { + return null; + } } } diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java index 53768714..87b58613 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java @@ -115,7 +115,11 @@ public class PgpDecryptionService { case OpenPgpApi.RESULT_CODE_SUCCESS: try { os.flush(); - message.setBody(os.toString()); + final String body = os.toString(); + if (body == null) { + throw new IOException("body was null"); + } + message.setBody(body); message.setEncryption(Message.ENCRYPTION_DECRYPTED); final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager(); if (message.trusted() @@ -130,9 +134,10 @@ public class PgpDecryptionService { break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: synchronized (PgpDecryptionService.this) { + PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); messages.addFirst(message); currentMessage = null; - storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)); + storePendingIntent(pendingIntent); } break; case OpenPgpApi.RESULT_CODE_ERROR: @@ -160,9 +165,10 @@ public class PgpDecryptionService { break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: synchronized (PgpDecryptionService.this) { + PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); messages.addFirst(message); currentMessage = null; - storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)); + storePendingIntent(pendingIntent); } break; case OpenPgpApi.RESULT_CODE_ERROR: diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 28085085..1bc5fa83 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -660,24 +660,25 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } public Pair<AxolotlCapability,Jid> isConversationAxolotlCapableDetailed(Conversation conversation) { - final List<Jid> jids = getCryptoTargets(conversation); - for(Jid jid : jids) { - if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) { - if (conversation.getAccount().getRoster().getContact(jid).trusted()) { - return new Pair<>(AxolotlCapability.MISSING_KEYS,jid); - } else { - return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid); + if (conversation.getMode() == Conversation.MODE_SINGLE + || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) { + final List<Jid> jids = getCryptoTargets(conversation); + for(Jid jid : jids) { + if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) { + if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) { + return new Pair<>(AxolotlCapability.MISSING_KEYS,jid); + } else { + return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid); + } } } - } - if (jids.size() > 0) { - return new Pair<>(AxolotlCapability.FULL, null); - } else { - if (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous()) { - return new Pair<>(AxolotlCapability.NO_MEMBERS, null); + if (jids.size() > 0) { + return new Pair<>(AxolotlCapability.FULL, null); } else { - return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null); + return new Pair<>(AxolotlCapability.NO_MEMBERS, null); } + } else { + return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null); } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index e8ec5426..0b3164f8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -91,7 +91,11 @@ public class XmppAxolotlMessage { private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); - this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + try { + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("invalid source id"); + } List<Element> keyElements = header.getChildren(); this.keys = new HashMap<>(keyElements.size()); for (Element keyElement : keyElements) { @@ -102,7 +106,7 @@ public class XmppAxolotlMessage { byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); this.keys.put(recipientId, key); } catch (NumberFormatException e) { - throw new IllegalArgumentException(e); + throw new IllegalArgumentException("invalid remote id"); } break; case IVTAG: diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 38b5a999..7d60dcf7 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -91,6 +91,17 @@ public class Account extends AbstractEntity { return pgpDecryptionService != null && pgpDecryptionService.isConnected(); } + public boolean setShowErrorNotification(boolean newValue) { + boolean oldValue = showErrorNotification(); + setKey("show_error",Boolean.toString(newValue)); + return newValue != oldValue; + } + + public boolean showErrorNotification() { + String key = getKey("show_error"); + return key == null || Boolean.parseBoolean(key); + } + public enum State { DISABLED, OFFLINE, @@ -192,7 +203,7 @@ public class Account extends AbstractEntity { protected int options = 0; protected String rosterVersion; protected State status = State.OFFLINE; - protected JSONObject keys = new JSONObject(); + protected final JSONObject keys; protected String avatar; protected String displayName = null; protected String hostname = null; @@ -227,11 +238,13 @@ public class Account extends AbstractEntity { this.password = password; this.options = options; this.rosterVersion = rosterVersion; + JSONObject tmp; try { - this.keys = new JSONObject(keys); - } catch (final JSONException ignored) { - this.keys = new JSONObject(); + tmp = new JSONObject(keys); + } catch(JSONException e) { + tmp = new JSONObject(); } + this.keys = tmp; this.avatar = avatar; this.displayName = displayName; this.hostname = hostname; @@ -380,15 +393,28 @@ public class Account extends AbstractEntity { } public String getKey(final String name) { - return this.keys.optString(name, null); + synchronized (this.keys) { + return this.keys.optString(name, null); + } } - public boolean setKey(final String keyName, final String keyValue) { + public int getKeyAsInt(final String name, int defaultValue) { + String key = getKey(name); try { - this.keys.put(keyName, keyValue); - return true; - } catch (final JSONException e) { - return false; + return key == null ? defaultValue : Integer.parseInt(key); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public boolean setKey(final String keyName, final String keyValue) { + synchronized (this.keys) { + try { + this.keys.put(keyName, keyValue); + return true; + } catch (final JSONException e) { + return false; + } } } @@ -408,7 +434,9 @@ public class Account extends AbstractEntity { values.put(SERVER, jid.getDomainpart()); values.put(PASSWORD, password); values.put(OPTIONS, options); - values.put(KEYS, this.keys.toString()); + synchronized (this.keys) { + values.put(KEYS, this.keys.toString()); + } values.put(ROSTERVERSION, rosterVersion); values.put(AVATAR, avatar); values.put(DISPLAY_NAME, displayName); @@ -485,54 +513,42 @@ public class Account extends AbstractEntity { } public String getPgpSignature() { - try { - if (keys.has(KEY_PGP_SIGNATURE) && !"null".equals(keys.getString(KEY_PGP_SIGNATURE))) { - return keys.getString(KEY_PGP_SIGNATURE); - } else { - return null; - } - } catch (final JSONException e) { - return null; - } + return getKey(KEY_PGP_SIGNATURE); } public boolean setPgpSignature(String signature) { - try { - keys.put(KEY_PGP_SIGNATURE, signature); - } catch (JSONException e) { - return false; - } - return true; + return setKey(KEY_PGP_SIGNATURE, signature); } public boolean unsetPgpSignature() { - try { - keys.put(KEY_PGP_SIGNATURE, JSONObject.NULL); - } catch (JSONException e) { - return false; + synchronized (this.keys) { + return keys.remove(KEY_PGP_SIGNATURE) != null; } - return true; } public long getPgpId() { - if (keys.has(KEY_PGP_ID)) { - try { - return keys.getLong(KEY_PGP_ID); - } catch (JSONException e) { + synchronized (this.keys) { + if (keys.has(KEY_PGP_ID)) { + try { + return keys.getLong(KEY_PGP_ID); + } catch (JSONException e) { + return 0; + } + } else { return 0; } - } else { - return 0; } } public boolean setPgpSignId(long pgpID) { - try { - keys.put(KEY_PGP_ID, pgpID); - } catch (JSONException e) { - return false; + synchronized (this.keys) { + try { + keys.put(KEY_PGP_ID, pgpID); + } catch (JSONException e) { + return false; + } + return true; } - return true; } public Roster getRoster() { diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index e512b586..70af45d4 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -118,7 +118,7 @@ public class Contact implements ListItem, Blockable { return this.systemName; } else if (this.serverName != null) { return this.serverName; - } else if (this.presenceName != null && trusted()) { + } else if (this.presenceName != null && mutualPresenceSubscription()) { return this.presenceName; } else if (jid.hasLocalpart()) { return jid.getLocalpart(); @@ -487,7 +487,7 @@ public class Contact implements ListItem, Blockable { } } - public boolean trusted() { + public boolean mutualPresenceSubscription() { return getOption(Options.FROM) && getOption(Options.TO); } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 1b38d349..e3577b06 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -124,7 +124,7 @@ public class Message extends AbstractEntity { this.conversationUuid = conversationUUid; this.counterpart = counterpart; this.trueCounterpart = trueCounterpart; - this.body = body; + this.body = body == null ? "" : body; this.timeSent = timeSent; this.encryption = encryption; this.status = status; @@ -266,6 +266,9 @@ public class Message extends AbstractEntity { } public void setBody(String body) { + if (body == null) { + throw new Error("You should not set the message body to null"); + } this.body = body; } @@ -539,7 +542,7 @@ public class Message extends AbstractEntity { public boolean trusted() { Contact contact = this.getContact(); - return (status > STATUS_RECEIVED || (contact != null && contact.trusted())); + return (status > STATUS_RECEIVED || (contact != null && contact.mutualPresenceSubscription())); } public boolean fixCounterpart() { diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 9d54a86d..fa6afcfa 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -127,7 +127,6 @@ public class MucOptions { UNKNOWN } - public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104"; public static final String STATUS_CODE_SELF_PRESENCE = "110"; public static final String STATUS_CODE_ROOM_CREATED = "201"; public static final String STATUS_CODE_BANNED = "301"; diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 884bc4af..cb9ffd96 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -254,10 +254,14 @@ public class IqGenerator extends AbstractGenerator { return iq; } - public IqPacket generateSetBlockRequest(final Jid jid) { + public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) { final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final Element block = iq.addChild("block", Xmlns.BLOCKING); - block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); + final Element item = block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); + if (reportSpam) { + item.addChild("report", "urn:xmpp:reporting:0").addChild("spam"); + } + Log.d(Config.LOGTAG,iq.toString()); return iq; } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 5ef47a33..f5c701cc 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -183,6 +183,10 @@ public class MessageGenerator extends AbstractGenerator { packet.setFrom(conversation.getAccount().getJid()); Element x = packet.addChild("x", "jabber:x:conference"); x.setAttribute("jid", conversation.getJid().toBareJid().toString()); + String password = conversation.getMucOptions().getPassword(); + if (password != null) { + x.setAttribute("password",password); + } return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 741ea98d..a9bffe3e 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -212,6 +212,8 @@ public class HttpDownloadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); } + connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); + connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000); connection.connect(); String contentLength = connection.getHeaderField("Content-Length"); connection.disconnect(); @@ -279,6 +281,8 @@ public class HttpDownloadConnection implements Transferable { long size = file.getSize(); connection.setRequestProperty("Range", "bytes="+size+"-"); } + connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); + connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000); connection.connect(); is = new BufferedInputStream(connection.getInputStream()); boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges")); diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 1bb70690..1bd6a8e4 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -171,10 +171,12 @@ public class HttpUploadConnection implements Transferable { connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); connection.setDoOutput(true); + connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); + connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000); connection.connect(); os = connection.getOutputStream(); transmitted = 0; - int count = -1; + int count; byte[] buffer = new byte[4096]; while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { transmitted += count; diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index a679d00c..49b0db21 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -139,7 +139,11 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { if(signedPreKeyPublic == null) { return null; } - return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); + try { + return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); + } catch (NumberFormatException e) { + return null; + } } public ECPublicKey signedPreKeyPublic(final Element bundle) { @@ -255,7 +259,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { Integer signedPreKeyId = signedPreKeyId(bundleElement); byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); IdentityKey identityKey = identityKey(bundleElement); - if(signedPreKeyPublic == null || identityKey == null) { + if(signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) { return null; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index f4fe80b5..81b68ed9 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -333,7 +333,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } if (timestamp == null) { - timestamp = AbstractParser.parseTimestamp(packet); + timestamp = AbstractParser.parseTimestamp(original,AbstractParser.parseTimestamp(packet)); } final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); @@ -539,7 +539,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece mXmppConnectionService.updateConversationUi(); } - if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { + if (mXmppConnectionService.confirmMessages() + && message.trusted() + && remoteMsgId != null + && !isForwarded + && !isTypeGroupChat) { sendMessageReceipts(account, packet); } @@ -583,26 +587,27 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } if (conversation != null && mucUserElement != null && from.isBareJid()) { - if (mucUserElement.hasChild("status")) { - for (Element child : mucUserElement.getChildren()) { - if (child.getName().equals("status") - && MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED.equals(child.getAttribute("code"))) { - mXmppConnectionService.fetchConferenceConfiguration(conversation); - } - } - } else if (mucUserElement.hasChild("item")) { - for(Element child : mucUserElement.getChildren()) { - if ("item".equals(child.getName())) { - MucOptions.User user = AbstractParser.parseItem(conversation,child); - Log.d(Config.LOGTAG,account.getJid()+": changing affiliation for " - +user.getRealJid()+" to "+user.getAffiliation()+" in " - +conversation.getJid().toBareJid()); - if (!user.realJidMatchesAccount()) { - conversation.getMucOptions().addUser(user); - mXmppConnectionService.getAvatarService().clear(conversation); - mXmppConnectionService.updateMucRosterUi(); - mXmppConnectionService.updateConversationUi(); + for (Element child : mucUserElement.getChildren()) { + if ("status".equals(child.getName())) { + try { + int code = Integer.parseInt(child.getAttribute("code")); + if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) { + mXmppConnectionService.fetchConferenceConfiguration(conversation); + break; } + } catch (Exception e) { + //ignored + } + } else if ("item".equals(child.getName())) { + MucOptions.User user = AbstractParser.parseItem(conversation,child); + Log.d(Config.LOGTAG,account.getJid()+": changing affiliation for " + +user.getRealJid()+" to "+user.getAffiliation()+" in " + +conversation.getJid().toBareJid()); + if (!user.realJidMatchesAccount()) { + conversation.getMucOptions().addUser(user); + mXmppConnectionService.getAvatarService().clear(conversation); + mXmppConnectionService.updateMucRosterUi(); + mXmppConnectionService.updateConversationUi(); } } } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 484b7b15..d8b6b4e1 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -1184,9 +1184,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); } - public void recreateAxolotlDb() { - recreateAxolotlDb(getWritableDatabase()); - } public void recreateAxolotlDb(SQLiteDatabase db) { Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 8fcda7c2..bc919788 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -14,7 +14,6 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.os.Environment; -import android.os.FileObserver; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.provider.OpenableColumns; @@ -58,7 +57,9 @@ import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.xmpp.pep.Avatar; public class FileBackend { - private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + private static final SimpleDateFormat IMAGE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); + + public static final String CONVERSATIONS_FILE_PROVIDER = "eu.siacs.conversations.files"; private XmppConnectionService mXmppConnectionService; @@ -226,6 +227,7 @@ public class FileBackend { } public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { + Log.d(Config.LOGTAG,"copy file ("+uri.toString()+") to private storage "+file.getAbsolutePath()); file.getParentFile().mkdirs(); OutputStream os = null; InputStream is = null; @@ -248,7 +250,6 @@ public class FileBackend { close(os); close(is); } - Log.d(Config.LOGTAG, "output file name " + file.getAbsolutePath()); } public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { @@ -338,6 +339,7 @@ public class FileBackend { } public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException { + Log.d(Config.LOGTAG,"copy image ("+image.toString()+") to private storage "+file.getAbsolutePath()); copyImageToPrivateStorage(file, image, 0); } @@ -431,16 +433,27 @@ public class FileBackend { return frame; } + private static String getTakePhotoPath() { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)+"/Camera/"; + } + public Uri getTakePhotoUri() { - StringBuilder pathBuilder = new StringBuilder(); - pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); - pathBuilder.append('/'); - pathBuilder.append("Camera"); - pathBuilder.append('/'); - pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg"); - File file = new File(pathBuilder.toString()); + File file = new File(getTakePhotoPath()+"IMG_" + this.IMAGE_DATE_FORMAT.format(new Date()) + ".jpg"); file.getParentFile().mkdirs(); - return FileProvider.getUriForFile(mXmppConnectionService,"eu.siacs.conversations.files",file); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file); + } else { + return Uri.fromFile(file); + } + } + + public static Uri getIndexableTakePhotoUri(Uri original) { + if ("file".equals(original.getScheme())) { + return original; + } else { + List<String> segments = original.getPathSegments(); + return Uri.parse("file://"+getTakePhotoPath()+segments.get(segments.size() - 1)); + } } public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) { diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 7cfe2433..6e1d6c4b 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.services; import android.app.Notification; import android.app.PendingIntent; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; @@ -14,18 +13,13 @@ import android.support.v4.app.NotificationCompat.BigPictureStyle; import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.RemoteInput; -import android.support.v4.app.TaskStackBuilder; import android.text.Html; import android.util.DisplayMetrics; import android.util.Log; -import org.json.JSONArray; -import org.json.JSONObject; - import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Calendar; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -297,11 +291,11 @@ public class NotificationService { modifyForTextOnly(mBuilder, messages); } RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build(); - NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation)).addRemoteInput(remoteInput).build(); + NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation, false)).addRemoteInput(remoteInput).build(); + NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build(); + mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mBuilder.addAction(action); - } else { - mBuilder.extend(new NotificationCompat.WearableExtender().addAction(action)); + mBuilder.addAction(replyAction); } if ((message = getFirstDownloadableMessage(messages)) != null) { mBuilder.addAction( @@ -474,11 +468,13 @@ public class NotificationService { return PendingIntent.getService(mXmppConnectionService, 0, intent, 0); } - private PendingIntent createReplyIntent(Conversation conversation) { + private PendingIntent createReplyIntent(Conversation conversation, boolean dismissAfterReply) { final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION); intent.putExtra("uuid",conversation.getUuid()); - return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 402361, intent, 0); + intent.putExtra("dismiss_notification",dismissAfterReply); + int id = conversation.getUuid().hashCode() % (dismissAfterReply ? 402359 : 426583); + return PendingIntent.getService(mXmppConnectionService, id, intent, 0); } private PendingIntent createDisableForeground() { @@ -494,11 +490,10 @@ public class NotificationService { return PendingIntent.getService(mXmppConnectionService, 45, intent, 0); } - private PendingIntent createDisableAccountIntent(final Account account) { + private PendingIntent createDismissErrorIntent() { final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); - intent.setAction(XmppConnectionService.ACTION_DISABLE_ACCOUNT); - intent.putExtra("account", account.getJid().toBareJid().toString()); - return PendingIntent.getService(mXmppConnectionService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS); + return PendingIntent.getService(mXmppConnectionService, 69, intent, 0); } private boolean wasHighlightedOrPrivate(final Message message) { @@ -592,7 +587,7 @@ public class NotificationService { final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService); final List<Account> errors = new ArrayList<>(); for (final Account account : mXmppConnectionService.getAccounts()) { - if (account.hasErrorStatus()) { + if (account.hasErrorStatus() && account.showErrorNotification()) { errors.add(account); } } @@ -613,27 +608,17 @@ public class NotificationService { mBuilder.addAction(R.drawable.ic_autorenew_white_24dp, mXmppConnectionService.getString(R.string.try_again), createTryAgainIntent()); - if (errors.size() == 1) { - mBuilder.addAction(R.drawable.ic_block_white_24dp, - mXmppConnectionService.getString(R.string.disable_account), - createDisableAccountIntent(errors.get(0))); - } - mBuilder.setOngoing(true); - //mBuilder.setLights(0xffffffff, 2000, 4000); + mBuilder.setDeleteIntent(createDismissErrorIntent()); + mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); } else { mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning); } - final TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService); - stackBuilder.addParentStack(ConversationActivity.class); - - final Intent manageAccountsIntent = new Intent(mXmppConnectionService, ManageAccountActivity.class); - stackBuilder.addNextIntent(manageAccountsIntent); - - final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); - - mBuilder.setContentIntent(resultPendingIntent); + mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, + 145, + new Intent(mXmppConnectionService,ManageAccountActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT)); notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3bf1e745..774842a7 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -132,8 +132,8 @@ public class XmppConnectionService extends Service { public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations"; public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; + public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error"; public static final String ACTION_TRY_AGAIN = "try_again"; - public static final String ACTION_DISABLE_ACCOUNT = "disable_account"; public static final String ACTION_IDLE_PING = "idle_ping"; private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh"; @@ -294,6 +294,9 @@ public class XmppConnectionService extends Service { mOnAccountUpdate.onAccountUpdate(); } if (account.getStatus() == Account.State.ONLINE) { + if (account.setShowErrorNotification(true)) { + databaseBackend.updateAccount(account); + } mMessageArchiveService.executePendingQueries(account); if (connection != null && connection.getFeatures().csi()) { if (checkListeners()) { @@ -323,7 +326,7 @@ public class XmppConnectionService extends Service { } account.pendingConferenceJoins.clear(); scheduleWakeUpCall(Config.PUSH_MODE ? Config.PING_MIN_INTERVAL : Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); - } else if (account.getStatus() == Account.State.OFFLINE) { + } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) { resetSendingToWaiting(account); final boolean disabled = account.isOptionSet(Account.OPTION_DISABLED); final boolean listeners = checkListeners(); @@ -340,6 +343,7 @@ public class XmppConnectionService extends Service { reconnectAccount(account, true, false); } else if ((account.getStatus() != Account.State.CONNECTING) && (account.getStatus() != Account.State.NO_INTERNET)) { + resetSendingToWaiting(account); if (connection != null) { int next = connection.getTimeToNextAttempt(); Log.d(Config.LOGTAG, account.getJid().toBareJid() @@ -526,6 +530,7 @@ public class XmppConnectionService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { final String action = intent == null ? null : intent.getAction(); + String pushedAccountHash = null; boolean interactive = false; if (action != null) { final Conversation c = findConversationByUuid(intent.getStringExtra("uuid")); @@ -554,28 +559,20 @@ public class XmppConnectionService extends Service { getPreferences().edit().putBoolean("keep_foreground_service", false).commit(); toggleForegroundService(); break; + case ACTION_DISMISS_ERROR_NOTIFICATIONS: + dismissErrorNotifications(); + break; case ACTION_TRY_AGAIN: resetAllAttemptCounts(false); interactive = true; break; - case ACTION_DISABLE_ACCOUNT: - try { - String jid = intent.getStringExtra("account"); - Account account = jid == null ? null : findAccountByJid(Jid.fromString(jid)); - if (account != null) { - account.setOption(Account.OPTION_DISABLED, true); - updateAccount(account); - } - } catch (final InvalidJidException ignored) { - break; - } - break; case ACTION_REPLY_TO_CONVERSATION: Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null && c != null) { - - String body = remoteInput.getString("text_reply"); - directReply(c,body); + final CharSequence body = remoteInput.getCharSequence("text_reply"); + if (body != null && body.length() > 0) { + directReply(c, body.toString(),intent.getBooleanExtra("dismiss_notification",false)); + } } break; case AudioManager.RINGER_MODE_CHANGED_ACTION: @@ -601,6 +598,7 @@ public class XmppConnectionService extends Service { break; case ACTION_GCM_MESSAGE_RECEIVED: Log.d(Config.LOGTAG,"gcm push message arrived in service. extras="+intent.getExtras()); + pushedAccountHash = intent.getStringExtra("account"); break; } } @@ -639,7 +637,7 @@ public class XmppConnectionService extends Service { } } else { pingCandidates.add(account); - if (msToNextPing <= 0) { + if (msToNextPing <= 0 || CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) { pingNow = true; } else { this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); @@ -707,7 +705,7 @@ public class XmppConnectionService extends Service { } } - private void directReply(Conversation conversation, String body) { + private void directReply(Conversation conversation, String body, final boolean dismissAfterReply) { Message message = new Message(conversation,body,conversation.getNextEncryption()); message.markUnread(); if (message.getEncryption() == Message.ENCRYPTION_PGP) { @@ -716,7 +714,11 @@ public class XmppConnectionService extends Service { public void success(Message message) { message.setEncryption(Message.ENCRYPTION_DECRYPTED); sendMessage(message); - mNotificationService.pushFromDirectReply(message); + if (dismissAfterReply) { + markRead(message.getConversation(),true); + } else { + mNotificationService.pushFromDirectReply(message); + } } @Override @@ -731,7 +733,11 @@ public class XmppConnectionService extends Service { }); } else { sendMessage(message); - mNotificationService.pushFromDirectReply(message); + if (dismissAfterReply) { + markRead(conversation,true); + } else { + mNotificationService.pushFromDirectReply(message); + } } } @@ -802,6 +808,21 @@ public class XmppConnectionService extends Service { connection.resetAttemptCount(); } } + if (account.setShowErrorNotification(true)) { + databaseBackend.updateAccount(account); + } + } + mNotificationService.updateErrorNotification(); + } + + private void dismissErrorNotifications() { + for (final Account account : this.accounts) { + if (account.hasErrorStatus()) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": dismissing error notification"); + if (account.setShowErrorNotification(false)) { + databaseBackend.updateAccount(account); + } + } } } @@ -976,8 +997,16 @@ public class XmppConnectionService extends Service { public XmppConnection createConnection(final Account account) { final SharedPreferences sharedPref = getPreferences(); - account.setResource(sharedPref.getString("resource", getString(R.string.default_resource)) - .toLowerCase(Locale.getDefault())); + String resource; + try { + resource = sharedPref.getString("resource", getString(R.string.default_resource)).toLowerCase(Locale.ENGLISH); + if (resource.trim().isEmpty()) { + throw new Exception(); + } + } catch (Exception e) { + resource = "conversations"; + } + account.setResource(resource); final XmppConnection connection = new XmppConnection(account, this); connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); @@ -1018,6 +1047,10 @@ public class XmppConnectionService extends Service { private void sendMessage(final Message message, final boolean resend, final boolean delay) { final Account account = message.getConversation().getAccount(); + if (account.setShowErrorNotification(true)) { + databaseBackend.updateAccount(account); + mNotificationService.updateErrorNotification(); + } final Conversation conversation = message.getConversation(); account.deactivateGracePeriod(); MessagePacket packet = null; @@ -1110,7 +1143,8 @@ public class XmppConnectionService extends Service { } if (packet != null) { - if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { + if (account.getXmppConnection().getFeatures().sm() + || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) { message.setStatus(Message.STATUS_UNSEND); } else { message.setStatus(Message.STATUS_SEND); @@ -1152,7 +1186,8 @@ public class XmppConnectionService extends Service { if (resend) { if (packet != null && addToConversation) { - if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { + if (account.getXmppConnection().getFeatures().sm() + || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) { markMessage(message, Message.STATUS_UNSEND); } else { markMessage(message, Message.STATUS_SEND); @@ -1288,6 +1323,13 @@ public class XmppConnectionService extends Service { for (Conversation conversation : conversations) { conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); checkDeletedFiles(conversation); + conversation.findUnsentTextMessages(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + markMessage(message, Message.STATUS_WAITING); + } + }); conversation.findUnreadMessages(new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { @@ -1508,7 +1550,6 @@ public class XmppConnectionService extends Service { conversation.setMode(Conversation.MODE_SINGLE); conversation.setContactJid(jid.toBareJid()); } - conversation.setNextEncryption(-1); conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); this.databaseBackend.updateConversation(conversation); } else { @@ -1549,7 +1590,6 @@ public class XmppConnectionService extends Service { public void archiveConversation(Conversation conversation) { getNotificationService().clear(conversation); conversation.setStatus(Conversation.STATUS_ARCHIVED); - conversation.setNextEncryption(-1); synchronized (this.conversations) { if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getAccount().getStatus() == Account.State.ONLINE) { @@ -1644,6 +1684,7 @@ public class XmppConnectionService extends Service { public boolean updateAccount(final Account account) { if (databaseBackend.updateAccount(account)) { + account.setShowErrorNotification(true); this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); reconnectAccountInBackground(account); @@ -3091,7 +3132,10 @@ public class XmppConnectionService extends Service { if (this.markRead(conversation)) { updateConversationUi(); } - if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) { + if (confirmMessages() + && markable != null + && markable.trusted() + && markable.getRemoteMsgId() != null) { Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString()); Account account = conversation.getAccount(); final Jid to = markable.getCounterpart(); @@ -3346,10 +3390,10 @@ public class XmppConnectionService extends Service { mDatabaseExecutor.execute(runnable); } - public void sendBlockRequest(final Blockable blockable) { + public void sendBlockRequest(final Blockable blockable, boolean reportSpam) { if (blockable != null && blockable.getBlockedJid() != null) { final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid), new OnIqPacketReceived() { + this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam), new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java index 9cf7e9f8..91ce34a9 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java @@ -3,6 +3,14 @@ package eu.siacs.conversations.ui; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.TypefaceSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Blockable; @@ -15,16 +23,31 @@ public final class BlockContactDialog { final AlertDialog.Builder builder = new AlertDialog.Builder(context); final boolean isBlocked = blockable.isBlocked(); builder.setNegativeButton(R.string.cancel, null); + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_block_contact,null); + TextView message = (TextView) view.findViewById(R.id.text); + final CheckBox report = (CheckBox) view.findViewById(R.id.report_spam); + final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); + report.setVisibility(!isBlocked && reporting ? View.VISIBLE : View.GONE); + builder.setView(view); + String value; + SpannableString spannable; if (blockable.getJid().isDomainJid() || blockable.getAccount().isBlocked(blockable.getJid().toDomainJid())) { builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); - builder.setMessage(context.getResources().getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text, - blockable.getJid().toDomainJid())); + value = blockable.getJid().toDomainJid().toString(); + spannable = new SpannableString(context.getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text, value)); + message.setText(spannable); } else { builder.setTitle(isBlocked ? R.string.action_unblock_contact : R.string.action_block_contact); - builder.setMessage(context.getResources().getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text, - blockable.getJid().toBareJid())); + value = blockable.getJid().toBareJid().toString(); + spannable = new SpannableString(context.getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text, value)); } + int start = spannable.toString().indexOf(value); + if (start >= 0) { + spannable.setSpan(new TypefaceSpan("monospace"),start,start + value.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + message.setText(spannable); builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, new DialogInterface.OnClickListener() { @Override @@ -32,7 +55,7 @@ public final class BlockContactDialog { if (isBlocked) { xmppConnectionService.sendUnblockRequest(blockable); } else { - xmppConnectionService.sendBlockRequest(blockable); + xmppConnectionService.sendBlockRequest(blockable, report.isChecked()); } } }); diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index e6f7fc5d..7791372a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -271,6 +271,15 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } @Override + protected void onStart() { + super.onStart(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } + } + + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case android.R.id.home: diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 836e345b..08128094 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -227,9 +227,14 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void onStart() { super.onStart(); - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false); - this.showLastSeen = preferences.getBoolean("last_activity", false); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } else { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + this.showDynamicTags = preferences.getBoolean("show_dynamic_tags", false); + this.showLastSeen = preferences.getBoolean("last_activity", false); + } } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 2c8b27b0..82b59dfc 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -153,11 +153,7 @@ public class ConversationActivity extends XmppActivity } public boolean isConversationsOverviewHideable() { - if (mContentView instanceof SlidingPaneLayout) { - return true; - } else { - return false; - } + return mContentView instanceof SlidingPaneLayout; } public boolean isConversationsOverviewVisable() { @@ -185,6 +181,7 @@ public class ConversationActivity extends XmppActivity } String pending = savedInstanceState.getString(STATE_PENDING_URI, null); if (pending != null) { + Log.d(Config.LOGTAG,"ConversationsActivity.onCreate() - restoring pending image uri"); mPendingImageUris.clear(); mPendingImageUris.add(Uri.parse(pending)); } @@ -454,14 +451,15 @@ public class ConversationActivity extends XmppActivity private boolean quickOmemoDebugger(Conversation c) { if (c != null) { + boolean single = c.getMode() == Conversation.MODE_SINGLE; AxolotlService axolotlService = c.getAccount().getAxolotlService(); Pair<AxolotlService.AxolotlCapability,Jid> capabilityJidPair = axolotlService.isConversationAxolotlCapableDetailed(c); switch (capabilityJidPair.first) { case MISSING_PRESENCE: - Toast.makeText(ConversationActivity.this,getString(R.string.missing_presence_subscription_with_x,capabilityJidPair.second.toBareJid().toString()),Toast.LENGTH_SHORT).show(); + Toast.makeText(ConversationActivity.this,single ? getString(R.string.missing_presence_subscription) : getString(R.string.missing_presence_subscription_with_x,capabilityJidPair.second.toBareJid().toString()),Toast.LENGTH_SHORT).show(); return true; case MISSING_KEYS: - Toast.makeText(ConversationActivity.this,getString(R.string.missing_keys_from_x,capabilityJidPair.second.toBareJid().toString()),Toast.LENGTH_SHORT).show(); + Toast.makeText(ConversationActivity.this,single ? getString(R.string.missing_omemo_keys) : getString(R.string.missing_keys_from_x,capabilityJidPair.second.toBareJid().toString()),Toast.LENGTH_SHORT).show(); return true; case WRONG_CONFIGURATION: Toast.makeText(ConversationActivity.this,R.string.wrong_conference_configuration, Toast.LENGTH_SHORT).show(); @@ -1150,6 +1148,7 @@ public class ConversationActivity extends XmppActivity } savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable()); if (this.mPendingImageUris.size() >= 1) { + Log.d(Config.LOGTAG,"ConversationsActivity.onSaveInstanceState() - saving pending image uri"); savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString()); } else { savedInstanceState.remove(STATE_PENDING_URI); @@ -1237,13 +1236,22 @@ public class ConversationActivity extends XmppActivity this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); } + final boolean stopping; + if (Build.VERSION.SDK_INT >= 17) { + stopping = isFinishing() || isDestroyed(); + } else { + stopping = isFinishing(); + } + if (!forbidProcessingPendings) { for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { Uri foo = i.next(); + Log.d(Config.LOGTAG,"ConversationsActivity.onBackendConnected() - attaching image to conversations. stopping="+Boolean.toString(stopping)); attachImageToConversation(getSelectedConversation(), foo); } for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG,"ConversationsActivity.onBackendConnected() - attaching file to conversations. stopping="+Boolean.toString(stopping)); attachFileToConversation(getSelectedConversation(), i.next()); } @@ -1368,6 +1376,7 @@ public class ConversationActivity extends XmppActivity mPendingImageUris.addAll(extractUriFromIntent(data)); if (xmppConnectionServiceBound) { for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG,"ConversationsActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); attachImageToConversation(getSelectedConversation(), i.next()); } } @@ -1381,6 +1390,7 @@ public class ConversationActivity extends XmppActivity mPendingFileUris.addAll(uris); if (xmppConnectionServiceBound) { for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG,"ConversationsActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE"); attachFileToConversation(c, i.next()); } } @@ -1395,8 +1405,10 @@ public class ConversationActivity extends XmppActivity } } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { if (mPendingImageUris.size() == 1) { - Uri uri = mPendingImageUris.get(0); + Uri uri = FileBackend.getIndexableTakePhotoUri(mPendingImageUris.get(0)); + mPendingImageUris.set(0, uri); if (xmppConnectionServiceBound) { + Log.d(Config.LOGTAG,"ConversationsActivity.onActivityResult() - attaching image to conversations. TAKE_PHOTO"); attachImageToConversation(getSelectedConversation(), uri); mPendingImageUris.clear(); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 14469355..df01e87c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -576,9 +576,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa downloadFile.setVisible(true); downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); } - if ((t != null && !(t instanceof TransferablePlaceholder)) - || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING - || m.getStatus() == Message.STATUS_OFFERED))) { + boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING + || m.getStatus() == Message.STATUS_UNSEND + || m.getStatus() == Message.STATUS_OFFERED; + if ((t != null && !(t instanceof TransferablePlaceholder)) || waitingOfferedSending && m.needsUploading()) { cancelTransmission.setVisible(true); } if (treatAsFile) { diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index c10d2741..cc178179 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -519,6 +519,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener); this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more); + if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) { + changeMoreTableVisibility(true); + } final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { @Override public void onCheckedChanged(final CompoundButton buttonView, @@ -585,9 +588,21 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); + if (showMoreInfo.isVisible()) { + showMoreInfo.setChecked(mMoreTable.getVisibility() == View.VISIBLE); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override protected void onStart() { super.onStart(); - if (getIntent() != null) { + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } else if (getIntent() != null) { try { this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid")); } catch (final InvalidJidException | NullPointerException ignored) { @@ -625,6 +640,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (mAccount != null) { savedInstanceState.putString("account", mAccount.getJid().toBareJid().toString()); savedInstanceState.putBoolean("initMode", mInitMode); + savedInstanceState.putBoolean("showMoreTable", mMoreTable.getVisibility() == View.VISIBLE); } super.onSaveInstanceState(savedInstanceState); } @@ -692,8 +708,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate startActivity(showBlocklistIntent); break; case R.id.action_server_info_show_more: - mMoreTable.setVisibility(item.isChecked() ? View.GONE : View.VISIBLE); - item.setChecked(!item.isChecked()); + changeMoreTableVisibility(!item.isChecked()); break; case R.id.action_change_password_on_server: gotoChangePassword(null); @@ -717,6 +732,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate return super.onOptionsItemSelected(item); } + private void changeMoreTableVisibility(boolean visible) { + mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE); + } + private void gotoChangePassword(String newPassword) { final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); diff --git a/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java b/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java deleted file mode 100644 index bedb4172..00000000 --- a/src/main/java/eu/siacs/conversations/ui/ExportLogsPreference.java +++ /dev/null @@ -1,36 +0,0 @@ -package eu.siacs.conversations.ui; - -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import android.preference.Preference; -import android.util.AttributeSet; - -import eu.siacs.conversations.services.ExportLogsService; - -public class ExportLogsPreference extends Preference { - - public ExportLogsPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public ExportLogsPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ExportLogsPreference(Context context) { - super(context); - } - - protected void onClick() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && getContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - return; - } - final Intent startIntent = new Intent(getContext(), ExportLogsService.class); - getContext().startService(startIntent); - super.onClick(); - } -}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java index f52ccb19..0f6b58ef 100644 --- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -103,6 +103,15 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } @Override + protected void onStart() { + super.onStart(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } + } + + @Override public void onSaveInstanceState(final Bundle savedInstanceState) { if (selectedAccount != null) { savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().toBareJid().toString()); diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 0752ae32..9a699b51 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -141,7 +141,7 @@ public class PublishProfilePictureActivity extends XmppActivity { } }); - this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext()); + this.defaultUri = PhoneHelper.getSelfiUri(getApplicationContext()); } private void chooseAvatar(boolean crop) { @@ -250,8 +250,11 @@ public class PublishProfilePictureActivity extends XmppActivity { if (!support) { this.hintOrWarning .setTextColor(getWarningTextColor()); - this.hintOrWarning - .setText(R.string.error_publish_avatar_no_server_support); + if (account.getStatus() == Account.State.ONLINE) { + this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); + } else { + this.hintOrWarning.setText(R.string.error_publish_avatar_offline); + } } } else { this.avatarUri = this.defaultUri; @@ -306,8 +309,11 @@ public class PublishProfilePictureActivity extends XmppActivity { } else { disablePublishButton(); this.hintOrWarning.setTextColor(getWarningTextColor()); - this.hintOrWarning - .setText(R.string.error_publish_avatar_no_server_support); + if (account.getStatus() == Account.State.ONLINE) { + this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); + } else { + this.hintOrWarning.setText(R.string.error_publish_avatar_offline); + } } if (this.defaultUri != null && uri.equals(this.defaultUri)) { this.secondaryHint.setVisibility(View.INVISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 17ade702..c2bf20ac 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -29,6 +29,8 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.ExportLogsService; import eu.siacs.conversations.xmpp.XmppConnection; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; public class SettingsActivity extends XmppActivity implements OnSharedPreferenceChangeListener { @@ -91,7 +93,7 @@ public class SettingsActivity extends XmppActivity implements displayToast(getString(R.string.toast_no_trusted_certs)); return true; } - final ArrayList selectedItems = new ArrayList<Integer>(); + final ArrayList selectedItems = new ArrayList<>(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this); dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title)); dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null, @@ -147,12 +149,72 @@ public class SettingsActivity extends XmppActivity implements exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - hasStoragePermission(REQUEST_WRITE_LOGS); + if (hasStoragePermission(REQUEST_WRITE_LOGS)) { + startExport(); + } + return true; + } + }); + + final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities"); + deleteOmemoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + deleteOmemoIdentities(); return true; } }); } + private void deleteOmemoIdentities() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.pref_delete_omemo_identities); + final List<CharSequence> accounts = new ArrayList<>(); + for(Account account : xmppConnectionService.getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + accounts.add(account.getJid().toBareJid().toString()); + } + } + final boolean[] checkedItems = new boolean[accounts.size()]; + builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + checkedItems[which] = isChecked; + final AlertDialog alertDialog = (AlertDialog) dialog; + for(boolean item : checkedItems) { + if (item) { + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); + return; + } + } + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); + } + }); + builder.setNegativeButton(R.string.cancel,null); + builder.setPositiveButton(R.string.delete_selected_keys, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + for(int i = 0; i < checkedItems.length; ++i) { + if (checkedItems[i]) { + try { + Jid jid = Jid.fromString(accounts.get(i).toString()); + Account account = xmppConnectionService.findAccountByJid(jid); + if (account != null) { + account.getAxolotlService().regenerateKeys(true); + } + } catch (InvalidJidException e) { + // + } + + } + } + } + }); + AlertDialog dialog = builder.create(); + dialog.show(); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + } + @Override public void onStop() { super.onStop(); @@ -213,13 +275,17 @@ public class SettingsActivity extends XmppActivity implements if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == REQUEST_WRITE_LOGS) { - getApplicationContext().startService(new Intent(getApplicationContext(), ExportLogsService.class)); + startExport(); } } else { Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); } } + private void startExport() { + startService(new Intent(getApplicationContext(), ExportLogsService.class)); + } + private void displayToast(final String msg) { runOnUiThread(new Runnable() { @Override diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 768961c1..ec5559ae 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -282,7 +282,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @Override public void onStart() { super.onStart(); - askForContactsPermissions(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } else { + askForContactsPermissions(); + } } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java index 3c837b94..59d58db5 100644 --- a/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.app.ActionBar; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -16,6 +17,11 @@ public class WelcomeActivity extends Activity { if (getResources().getBoolean(R.bool.portrait_only)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } + final ActionBar ab = getActionBar(); + if (ab != null) { + ab.setDisplayShowHomeEnabled(false); + ab.setDisplayHomeAsUpEnabled(false); + } super.onCreate(savedInstanceState); setContentView(R.layout.welcome); final Button createAccount = (Button) findViewById(R.id.create_account); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index b24c9539..4505e632 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -51,6 +51,7 @@ 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.persistance.FileBackend; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.widget.ClickableMovementMethod; import eu.siacs.conversations.ui.widget.ListSelectionManager; @@ -710,7 +711,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { try { - uri = FileProvider.getUriForFile(activity, "eu.siacs.conversations.files", file); + uri = FileProvider.getUriForFile(activity, FileBackend.CONVERSATIONS_FILE_PROVIDER, file); } catch (IllegalArgumentException e) { Toast.makeText(activity,activity.getString(R.string.no_permission_to_access_x,file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); return; diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 429c349a..38ebced1 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -25,6 +25,7 @@ import java.util.regex.Pattern; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -204,6 +205,15 @@ public final class CryptoHelper { return prettifyFingerprintCert(bytesToHex(fingerprint)); } + public static String getAccountFingerprint(Account account) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + return bytesToHex(md.digest(account.getJid().toBareJid().toString().getBytes("UTF-8"))); + } catch (Exception e) { + return ""; + } + } + public static int encryptionTypeToText(int encryption) { switch (encryption) { case Message.ENCRYPTION_OTR: diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index e3a24fcf..3242cfcf 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -93,14 +93,14 @@ public class PhoneHelper { try { return (super.loadInBackground()); - } catch (SecurityException e) { + } catch (Throwable e) { return(null); } } } - public static Uri getSefliUri(Context context) { + public static Uri getSelfiUri(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { return null; diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java index 768e9f17..81f93653 100644 --- a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java +++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java @@ -12,6 +12,8 @@ import eu.siacs.conversations.Config; public class SocksSocketFactory { + private static final byte[] LOCALHOST = new byte[]{127,0,0,1}; + public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { InputStream proxyIs = socket.getInputStream(); OutputStream proxyOs = socket.getOutputStream(); @@ -44,7 +46,7 @@ public class SocksSocketFactory { } public static Socket createSocketOverTor(String destination, int port) throws IOException { - return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 9050), destination, port); + return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port); } static class SocksConnectionException extends IOException { diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index bf997bc1..faf4b3a5 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -181,9 +181,7 @@ public class UIHelper { } } else { String body = message.getBody(); - if (body == null) { - body = ""; - } else if (body.length() > 256) { + if (body.length() > 256) { body = body.substring(0,256); } if (body.startsWith(Message.ME_COMMAND)) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index e3af48e3..383e990d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -136,7 +136,7 @@ public class XmppConnection implements Runnable { private SaslMechanism saslMechanism; - private X509KeyManager mKeyManager = new X509KeyManager() { + private class MyKeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { return account.getPrivateKeyAlias(); @@ -149,9 +149,11 @@ public class XmppConnection implements Runnable { @Override public X509Certificate[] getCertificateChain(String alias) { + Log.d(Config.LOGTAG,"getting certificate chain"); try { return KeyChain.getCertificateChain(mXmppConnectionService, alias); } catch (Exception e) { + Log.d(Config.LOGTAG,e.getMessage()); return new X509Certificate[0]; } } @@ -174,7 +176,8 @@ public class XmppConnection implements Runnable { return null; } } - }; + } + private Identity mServerIdentity = Identity.UNKNOWN; public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() { @@ -241,6 +244,9 @@ public class XmppConnection implements Runnable { } protected void connect() { + if (mXmppConnectionService.areMessagesInitialized()) { + mXmppConnectionService.resetSendingToWaiting(account); + } Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting"); features.encryptionEnabled = false; this.attempt++; @@ -458,7 +464,7 @@ public class XmppConnection implements Runnable { MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); KeyManager[] keyManager; if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { - keyManager = new KeyManager[]{mKeyManager}; + keyManager = new KeyManager[]{new MyKeyManager()}; } else { keyManager = null; } @@ -846,18 +852,13 @@ public class XmppConnection implements Runnable { saslMechanism = new Anonymous(tagWriter, account, mXmppConnectionService.getRNG()); } if (saslMechanism != null) { - final JSONObject keys = account.getKeys(); - try { - if (keys.has(Account.PINNED_MECHANISM_KEY) && - keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) { - Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() + - " has lower priority (" + String.valueOf(saslMechanism.getPriority()) + - ") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) + - "). Possible downgrade attack?"); - throw new SecurityException(); - } - } catch (final JSONException e) { - Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism"); + final int pinnedMechanism = account.getKeyAsInt(Account.PINNED_MECHANISM_KEY, -1); + if (pinnedMechanism > saslMechanism.getPriority()) { + Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() + + " has lower priority (" + String.valueOf(saslMechanism.getPriority()) + + ") than pinned priority (" + pinnedMechanism + + "). Possible downgrade attack?"); + throw new SecurityException(); } Log.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism()); auth.setAttribute("mechanism", saslMechanism.getMechanism()); @@ -1072,7 +1073,7 @@ public class XmppConnection implements Runnable { this.disco.clear(); } mPendingServiceDiscoveries.set(0); - mWaitForDisco.set(mServerIdentity != Identity.NIMBUZZ); + mWaitForDisco.set(mServerIdentity != Identity.NIMBUZZ && smVersion != 0); lastDiscoStarted = SystemClock.elapsedRealtime(); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery"); mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); @@ -1600,6 +1601,10 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING); } + public boolean spamReporting() { + return hasDiscoFeature(account.getServer(), "urn:xmpp:reporting:reason:spam:0"); + } + public boolean register() { return hasDiscoFeature(account.getServer(), Xmlns.REGISTER); } |