aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java9
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/OtrService.java21
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java8
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/PgpEngine.java30
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java1028
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java1041
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java207
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java12
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java10
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/sasl/External.java1
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java26
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Bookmark.java7
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java8
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java32
-rw-r--r--src/main/java/eu/siacs/conversations/entities/ListItem.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java235
-rw-r--r--src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java2
-rw-r--r--src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java37
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java52
-rw-r--r--src/main/java/eu/siacs/conversations/generator/MessageGenerator.java24
-rw-r--r--src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java5
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java2
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java188
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java36
-rw-r--r--src/main/java/eu/siacs/conversations/parser/AbstractParser.java35
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java14
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java92
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java32
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java19
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java558
-rw-r--r--src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java10
-rw-r--r--src/main/java/eu/siacs/conversations/services/AvatarService.java303
-rw-r--r--src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java6
-rw-r--r--src/main/java/eu/siacs/conversations/services/ExportLogsService.java1
-rw-r--r--src/main/java/eu/siacs/conversations/services/MessageArchiveService.java52
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java69
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java631
-rw-r--r--src/main/java/eu/siacs/conversations/ui/AboutPreference.java4
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java11
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java11
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java80
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java354
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java21
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditMessage.java6
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java4
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java13
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java98
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java40
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java20
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java67
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java12
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java61
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java18
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java165
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java143
-rw-r--r--src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java103
-rw-r--r--src/main/java/eu/siacs/conversations/ui/widget/Switch.java68
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java259
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java18
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExifHelper.java14
-rw-r--r--src/main/java/eu/siacs/conversations/utils/FileUtils.java49
-rw-r--r--src/main/java/eu/siacs/conversations/utils/PRNGFixes.java5
-rw-r--r--src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java4
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java53
-rw-r--r--src/main/java/eu/siacs/conversations/xml/Element.java5
-rw-r--r--src/main/java/eu/siacs/conversations/xml/XmlReader.java4
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java184
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java161
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java20
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java49
73 files changed, 3393 insertions, 3589 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index e39cd654..25f08f30 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations;
import android.graphics.Bitmap;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
public final class Config {
@@ -27,7 +28,7 @@ public final class Config {
}
public static boolean supportOmemo() {
- return (ENCRYPTION_MASK & OMEMO) != 0;
+ return ConversationsPlusPreferences.omemoEnabled();
}
public static boolean multipleEncryptionChoices() {
@@ -45,7 +46,6 @@ public final class Config {
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true
- public static final boolean FORCE_ORBOT = false; // always use TOR
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;
public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification
@@ -65,7 +65,7 @@ public final class Config {
public static final boolean CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND = false;
public static final int AVATAR_SIZE = 192;
- public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
+ public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG;
public static final int IMAGE_SIZE = 1920;
public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
@@ -74,9 +74,12 @@ public final class Config {
public static final int MESSAGE_MERGE_WINDOW = 20;
+ public static final boolean UTF8_EMOTICONS = false;
+
public static final int PAGE_SIZE = 50;
public static final int MAX_NUM_PAGES = 3;
+ public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
public static final int REFRESH_UI_INTERVAL = 500;
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java
index 1804704e..4ddf51fb 100644
--- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java
@@ -1,7 +1,5 @@
package eu.siacs.conversations.crypto;
-import android.util.Log;
-
import net.java.otr4j.OtrEngineHost;
import net.java.otr4j.OtrException;
import net.java.otr4j.OtrPolicy;
@@ -26,6 +24,8 @@ import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
@@ -110,7 +110,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
mXmppConnectionService.updateConversationUi();
}
} catch (InvalidJidException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
}
}
@@ -151,7 +151,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
this.saveKey();
mXmppConnectionService.databaseBackend.updateAccount(account);
} catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"error generating key pair " + e.getMessage());
}
}
@@ -185,7 +185,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
Jid jid = Jid.fromSessionID(session);
Conversation conversation = mXmppConnectionService.find(account,jid);
if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
- if (mXmppConnectionService.sendChatStates()) {
+ if (ConversationsPlusPreferences.chatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
}
}
@@ -217,7 +217,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void showError(SessionID arg0, String arg1) throws OtrException {
- Log.d(Config.LOGTAG,"show error");
+ Logging.d(Config.LOGTAG,"show error");
}
@Override
@@ -252,7 +252,8 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void unreadableMessageReceived(SessionID session) throws OtrException {
- Log.d(Config.LOGTAG,"unreadable message received");
+ Logging.d(Config.LOGTAG,"unreadable message received");
+ // Hier update des contents fuer FS#96
sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
}
@@ -266,8 +267,8 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
.generateOtrError(jid, id, errorText);
packet.setFrom(account.getJid());
mXmppConnectionService.sendMessagePacket(account,packet);
- Log.d(Config.LOGTAG,packet.toString());
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()
+ Logging.d(Config.LOGTAG,packet.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()
+": unreadable OTR message in "+conversation.getName());
}
} catch (InvalidJidException e) {
@@ -282,7 +283,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void verify(SessionID id, String fingerprint, boolean approved) {
- Log.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
+ Logging.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
try {
final Jid jid = Jid.fromSessionID(id);
Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
index ed67dc65..5afbe5c4 100644
--- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
@@ -2,15 +2,15 @@ package eu.siacs.conversations.crypto;
import android.app.PendingIntent;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.UiCallback;
-
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.UiCallback;
+
public class PgpDecryptionService {
private final XmppConnectionService xmppConnectionService;
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
index 7ca5bea0..56ca26da 100644
--- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
+++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
@@ -16,6 +16,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -59,7 +62,8 @@ public class PgpEngine {
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
if (message.trusted()
&& message.treatAsDownloadable() != Message.Decision.NEVER
- && manager.getAutoAcceptFileSize() > 0) {
+ && ConversationsPlusPreferences.autoDownloadFileLink()
+ && ConversationsPlusPreferences.autoAcceptFileSize() > 0) {
manager.createNewDownloadConnection(message);
}
mXmppConnectionService.updateMessage(message);
@@ -83,10 +87,8 @@ public class PgpEngine {
});
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
try {
- final DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
- final DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
+ final DownloadableFile inputFile = FileBackend.getFile(message, false);
+ final DownloadableFile outputFile = FileBackend.getFile(message, true);
outputFile.getParentFile().mkdirs();
outputFile.createNewFile();
InputStream is = new FileInputStream(inputFile);
@@ -100,12 +102,12 @@ public class PgpEngine {
OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
URL url = message.getFileParams().url;
- mXmppConnectionService.getFileBackend().updateFileParams(message,url);
+ MessageUtil.updateFileParams(message, url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
PgpEngine.this.mXmppConnectionService
.updateMessage(message);
inputFile.delete();
- mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
+ FileBackend.updateMediaScanner(outputFile, mXmppConnectionService);
callback.success(message);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
@@ -164,7 +166,7 @@ public class PgpEngine {
String[] lines = os.toString().split("\n");
for (int i = 2; i < lines.length - 1; ++i) {
if (!lines[i].contains("Version")) {
- encryptedMessageBody.append(lines[i].trim());
+ encryptedMessageBody.append(lines[i]);
}
}
message.setEncryptedBody(encryptedMessageBody
@@ -188,10 +190,8 @@ public class PgpEngine {
});
} else {
try {
- DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
- DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
+ DownloadableFile inputFile = FileBackend.getFile(message, true);
+ DownloadableFile outputFile = FileBackend.getFile(message, false);
outputFile.getParentFile().mkdirs();
outputFile.createNewFile();
final InputStream is = new FileInputStream(inputFile);
@@ -209,7 +209,7 @@ public class PgpEngine {
} catch (IOException ignored) {
//ignored
}
- FileBackend.close(os);
+ StreamUtil.close(os);
callback.success(message);
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
@@ -246,7 +246,7 @@ public class PgpEngine {
pgpSig.append("-----BEGIN PGP SIGNATURE-----");
pgpSig.append('\n');
pgpSig.append('\n');
- pgpSig.append(signature.replace("\n", "").trim());
+ pgpSig.append(signature.replace("\n", ""));
pgpSig.append('\n');
pgpSig.append("-----END PGP SIGNATURE-----");
Intent params = new Intent();
@@ -326,7 +326,7 @@ public class PgpEngine {
sig = false;
} else {
if (!line.contains("Version")) {
- signatureBuilder.append(line.trim());
+ signatureBuilder.append(line);
}
}
}
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 cc5c2491..e490ac64 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -1,211 +1,39 @@
package eu.siacs.conversations.crypto.axolotl;
-import android.os.Bundle;
-import android.security.KeyChain;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.util.Log;
-import android.util.Pair;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.IdentityKey;
-import org.whispersystems.libaxolotl.IdentityKeyPair;
-import org.whispersystems.libaxolotl.InvalidKeyException;
-import org.whispersystems.libaxolotl.InvalidKeyIdException;
-import org.whispersystems.libaxolotl.SessionBuilder;
-import org.whispersystems.libaxolotl.UntrustedIdentityException;
-import org.whispersystems.libaxolotl.ecc.ECPublicKey;
-import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
-import org.whispersystems.libaxolotl.util.KeyHelper;
-import java.security.PrivateKey;
-import java.security.Security;
-import java.security.Signature;
import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Random;
import java.util.Set;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.parser.IqParser;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
-import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
-import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
+/**
+ * Created by tzur on 02.03.2016.
+ */
+public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded {
- public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
- public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
- public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
- public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
+ String LOGPREFIX = "AxolotlService";
- public static final String LOGPREFIX = "AxolotlService";
+ String PEP_PREFIX = "eu.siacs.conversations.axolotl";
+ String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
+ String PEP_BUNDLES = PEP_PREFIX + ".bundles";
+ String PEP_VERIFICATION = PEP_PREFIX + ".verification";
- public static final int NUM_KEYS_TO_PUBLISH = 100;
- public static final int publishTriesThreshold = 3;
+ int NUM_KEYS_TO_PUBLISH = 100;
- private final Account account;
- private final XmppConnectionService mXmppConnectionService;
- private final SQLiteAxolotlStore axolotlStore;
- private final SessionMap sessions;
- private final Map<Jid, Set<Integer>> deviceIds;
- private final Map<String, XmppAxolotlMessage> messageCache;
- private final FetchStatusMap fetchStatusMap;
- private final SerialSingleThreadExecutor executor;
- private int numPublishTriesOnEmptyPep = 0;
- private boolean pepBroken = false;
-
- @Override
- public void onAdvancedStreamFeaturesAvailable(Account account) {
- if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) {
- publishBundlesIfNeeded(true, false);
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
- }
- }
-
- public boolean fetchMapHasErrors(List<Jid> jids) {
- for(Jid jid : jids) {
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private static class AxolotlAddressMap<T> {
- protected Map<String, Map<Integer, T>> map;
- protected final Object MAP_LOCK = new Object();
-
- public AxolotlAddressMap() {
- this.map = new HashMap<>();
- }
-
- public void put(AxolotlAddress address, T value) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- devices = new HashMap<>();
- map.put(address.getName(), devices);
- }
- devices.put(address.getDeviceId(), value);
- }
- }
-
- public T get(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- return null;
- }
- return devices.get(address.getDeviceId());
- }
- }
-
- public Map<Integer, T> getAll(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- return new HashMap<>();
- }
- return devices;
- }
- }
-
- public boolean hasAny(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- return devices != null && !devices.isEmpty();
- }
- }
-
- public void clear() {
- map.clear();
- }
-
- }
-
- private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
- private final XmppConnectionService xmppConnectionService;
- private final Account account;
-
- public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
- super();
- this.xmppConnectionService = service;
- this.account = account;
- this.fillMap(store);
- }
-
- private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
- for (Integer deviceId : deviceIds) {
- AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
- IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
- if(Config.X509_VERIFICATION) {
- X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
- if (certificate != null) {
- Bundle information = CryptoHelper.extractCertificateInformation(certificate);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.fromString(bareJid);
- Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final InvalidJidException ignored) {
- //ignored
- }
- }
- }
- this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
- }
- }
-
- private void fillMap(SQLiteAxolotlStore store) {
- List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString());
- putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store);
- for (Contact contact : account.getRoster().getContacts()) {
- Jid bareJid = contact.getJid().toBareJid();
- String address = bareJid.toString();
- deviceIds = store.getSubDeviceSessions(address);
- putDevicesForJid(address, deviceIds, store);
- }
-
- }
-
- @Override
- public void put(AxolotlAddress address, XmppAxolotlSession value) {
- super.put(address, value);
- value.setNotFresh();
- xmppConnectionService.syncRosterToDisk(account);
- }
-
- public void put(XmppAxolotlSession session) {
- this.put(session.getRemoteAddress(), session);
- }
- }
-
- public enum FetchStatus {
+ enum FetchStatus {
PENDING,
SUCCESS,
SUCCESS_VERIFIED,
@@ -213,836 +41,78 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
ERROR
}
- private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
+ String getOwnFingerprint();
- }
+ Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust);
- public static String getLogprefix(Account account) {
- return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
- }
- public AxolotlService(Account account, XmppConnectionService connectionService) {
- if (Security.getProvider("BC") == null) {
- Security.addProvider(new BouncyCastleProvider());
- }
- this.mXmppConnectionService = connectionService;
- this.account = account;
- this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
- this.deviceIds = new HashMap<>();
- this.messageCache = new HashMap<>();
- this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
- this.fetchStatusMap = new FetchStatusMap();
- this.executor = new SerialSingleThreadExecutor();
- }
+ Set<String> getFingerprintsForOwnSessions();
- public String getOwnFingerprint() {
- return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
- }
+ Set<String> getFingerprintsForContact(Contact contact);
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
- return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
- }
+ boolean isPepBroken();
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
- return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust);
- }
+ void regenerateKeys(boolean wipeOther);
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
- Set<IdentityKey> keys = new HashSet<>();
- for(Jid jid : jids) {
- keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust));
- }
- return keys;
- }
+ int getOwnDeviceId();
- public long getNumTrustedKeys(Jid jid) {
- return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString());
- }
-
- public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
- for(Jid jid : jids) {
- if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) {
- return true;
- }
- }
- return false;
- }
-
- private AxolotlAddress getAddressForJid(Jid jid) {
- return new AxolotlAddress(jid.toString(), 0);
- }
-
- private Set<XmppAxolotlSession> findOwnSessions() {
- AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
- return new HashSet<>(this.sessions.getAll(ownAddress).values());
- }
-
- private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
- AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
- return new HashSet<>(this.sessions.getAll(contactAddress).values());
- }
-
- private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
- HashSet<XmppAxolotlSession> sessions = new HashSet<>();
- for(Jid jid : conversation.getAcceptedCryptoTargets()) {
- sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
- }
- return sessions;
- }
-
- public Set<String> getFingerprintsForOwnSessions() {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findOwnSessions()) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
- public Set<String> getFingerprintsForContact(final Contact contact) {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findSessionsForContact(contact)) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
+ Set<Integer> getOwnDeviceIds();
- private boolean hasAny(Jid jid) {
- return sessions.hasAny(getAddressForJid(jid));
- }
+ void registerDevices(Jid jid, @NonNull Set<Integer> deviceIds);
- public boolean isPepBroken() {
- return this.pepBroken;
- }
+ void wipeOtherPepDevices();
- public void regenerateKeys(boolean wipeOther) {
- axolotlStore.regenerate();
- sessions.clear();
- fetchStatusMap.clear();
- publishBundlesIfNeeded(true, wipeOther);
- }
+ void purgeKey(String fingerprint);
- public int getOwnDeviceId() {
- return axolotlStore.getLocalRegistrationId();
- }
-
- public Set<Integer> getOwnDeviceIds() {
- return this.deviceIds.get(account.getJid().toBareJid());
- }
+ void publishOwnDeviceIdIfNeeded();
- private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
- final XmppAxolotlSession.Trust from,
- final XmppAxolotlSession.Trust to) {
- for (Integer deviceId : deviceIds) {
- AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
- XmppAxolotlSession session = sessions.get(address);
- if (session != null && session.getFingerprint() != null
- && session.getTrust() == from) {
- session.setTrust(to);
- }
- }
- }
+ void publishOwnDeviceId(Set<Integer> deviceIds);
- public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if (jid.toBareJid().equals(account.getJid().toBareJid())) {
- if (!deviceIds.isEmpty()) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status.");
- pepBroken = false;
- numPublishTriesOnEmptyPep = 0;
- }
- if (deviceIds.contains(getOwnDeviceId())) {
- deviceIds.remove(getOwnDeviceId());
- } else {
- publishOwnDeviceId(deviceIds);
- }
- for (Integer deviceId : deviceIds) {
- AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
- if (sessions.get(ownDeviceAddress) == null) {
- buildSessionFromPEP(ownDeviceAddress);
- }
- }
- }
- Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
- expiredDevices.removeAll(deviceIds);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED,
- XmppAxolotlSession.Trust.INACTIVE_TRUSTED);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509,
- XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED,
- XmppAxolotlSession.Trust.INACTIVE_UNDECIDED);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED,
- XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED);
- Set<Integer> newDevices = new HashSet<>(deviceIds);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED,
- XmppAxolotlSession.Trust.TRUSTED);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509,
- XmppAxolotlSession.Trust.TRUSTED_X509);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED,
- XmppAxolotlSession.Trust.UNDECIDED);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED,
- XmppAxolotlSession.Trust.UNTRUSTED);
- this.deviceIds.put(jid, deviceIds);
- mXmppConnectionService.keyStatusUpdated(null);
- }
+ void publishDeviceVerificationAndBundle(SignedPreKeyRecord signedPreKeyRecord,
+ Set<PreKeyRecord> preKeyRecords,
+ boolean announceAfter,
+ boolean wipe);
- public void wipeOtherPepDevices() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
- return;
- }
- Set<Integer> deviceIds = new HashSet<>();
- deviceIds.add(getOwnDeviceId());
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- // TODO: implement this!
- }
- });
- }
+ void publishBundlesIfNeeded(boolean announce, boolean wipe);
- public void purgeKey(final String fingerprint) {
- axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
- }
+ XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint);
- public void publishOwnDeviceIdIfNeeded() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
- } else {
- Element item = mXmppConnectionService.getIqParser().getItem(packet);
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- if (!deviceIds.contains(getOwnDeviceId())) {
- publishOwnDeviceId(deviceIds);
- }
- }
- }
- });
- }
+ X509Certificate getFingerprintCertificate(String fingerprint);
- public void publishOwnDeviceId(Set<Integer> deviceIds) {
- Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
- if (!deviceIdsCopy.contains(getOwnDeviceId())) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
- if (deviceIdsCopy.isEmpty()) {
- if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
- pepBroken = true;
- return;
- } else {
- numPublishTriesOnEmptyPep++;
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
- }
- } else {
- numPublishTriesOnEmptyPep = 0;
- }
- deviceIdsCopy.add(getOwnDeviceId());
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() != IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
- }
- }
- });
- }
- }
+ void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust);
- public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
- final Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- try {
- IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
- PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
- X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
- verifier.update(axolotlPublicKey.serialize());
- byte[] signature = verifier.sign();
- IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
+ Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation);
- public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
+ boolean createSessionsIfNeeded(Conversation conversation);
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- return; //ignore timeout. do nothing
- }
-
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Element error = packet.findChild("error");
- if (error == null || !error.hasChild("item-not-found")) {
- pepBroken = true;
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
- return;
- }
- }
-
- PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
- Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
- boolean flush = false;
- if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
- bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
- flush = true;
- }
- if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
- }
- try {
- boolean changed = false;
- // Validate IdentityKey
- IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
- if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
- changed = true;
- }
-
- // Validate signedPreKeyRecord + ID
- SignedPreKeyRecord signedPreKeyRecord;
- int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
- try {
- signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if (flush
- || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
- || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
- } catch (InvalidKeyIdException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
-
- // Validate PreKeys
- Set<PreKeyRecord> preKeyRecords = new HashSet<>();
- if (keys != null) {
- for (Integer id : keys.keySet()) {
- try {
- PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
- if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
- preKeyRecords.add(preKeyRecord);
- }
- } catch (InvalidKeyIdException ignored) {
- }
- }
- }
- int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
- if (newKeys > 0) {
- List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId() + 1, newKeys);
- preKeyRecords.addAll(newRecords);
- for (PreKeyRecord record : newRecords) {
- axolotlStore.storePreKey(record.getId(), record);
- }
- changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
- }
-
-
- if (changed) {
- if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
- mXmppConnectionService.publishDisplayName(account);
- publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- } else {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announce) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- }
- } catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
- }
- }
- });
- }
-
- private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
- Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
- signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
- preKeyRecords, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announceAfter) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
- }
- }
- });
- }
-
- public boolean isConversationAxolotlCapable(Conversation conversation) {
- final List<Jid> jids = getCryptoTargets(conversation);
- for(Jid jid : jids) {
- if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
- return false;
- }
- }
- return jids.size() > 0;
- }
-
- public List<Jid> getCryptoTargets(Conversation conversation) {
- final List<Jid> jids;
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- jids = Arrays.asList(conversation.getJid().toBareJid());
- } else {
- jids = conversation.getMucOptions().getMembers();
- jids.remove(account.getJid().toBareJid());
- }
- return jids;
- }
-
- public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
- return axolotlStore.getFingerprintTrust(fingerprint);
- }
-
- public X509Certificate getFingerprintCertificate(String fingerprint) {
- return axolotlStore.getFingerprintCertificate(fingerprint);
- }
-
- public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
- axolotlStore.setFingerprintTrust(fingerprint, trust);
- }
-
- private void verifySessionWithPEP(final XmppAxolotlSession session) {
- Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
- final AxolotlAddress address = session.getRemoteAddress();
- final IdentityKey identityKey = session.getIdentityKey();
- try {
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
- if (verification != null) {
- try {
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initVerify(verification.first[0]);
- verifier.update(identityKey.serialize());
- if (verifier.verify(verification.second)) {
- try {
- mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
- String fingerprint = session.getFingerprint();
- Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
- setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
- axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
- fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
- Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.fromString(address.getName());
- Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final InvalidJidException ignored) {
- //ignored
- }
- finishBuildingSessionsFromPEP(address);
- return;
- } catch (Exception e) {
- Log.d(Config.LOGTAG,"could not verify certificate");
- }
- }
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
- }
- } else {
- Log.d(Config.LOGTAG,"no verification found");
- }
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- });
- } catch (InvalidJidException e) {
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- }
-
- private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
- if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
- && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
- FetchStatus report = null;
- if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
- | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
- report = FetchStatus.SUCCESS_VERIFIED;
- } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
- || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
- report = FetchStatus.ERROR;
- }
- mXmppConnectionService.keyStatusUpdated(report);
- }
- }
-
- private void buildSessionFromPEP(final AxolotlAddress address) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
- if (address.getDeviceId() == getOwnDeviceId()) {
- throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
- }
-
- try {
- IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
- Jid.fromString(address.getName()), address.getDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
- mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.TIMEOUT);
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
- final IqParser parser = mXmppConnectionService.getIqParser();
- final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
- final PreKeyBundle bundle = parser.bundle(packet);
- if (preKeyBundleList.isEmpty() || bundle == null) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- return;
- }
- Random random = new Random();
- final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
- if (preKey == null) {
- //should never happen
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- return;
- }
-
- final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
- preKey.getPreKeyId(), preKey.getPreKey(),
- bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
- bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
-
- try {
- SessionBuilder builder = new SessionBuilder(axolotlStore, address);
- builder.process(preKeyBundle);
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
- sessions.put(address, session);
- if (Config.X509_VERIFICATION) {
- verifySessionWithPEP(session);
- } else {
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- } catch (UntrustedIdentityException | InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
- + e.getClass().getName() + ", " + e.getMessage());
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- }
- } else {
- fetchStatusMap.put(address, FetchStatus.ERROR);
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
- finishBuildingSessionsFromPEP(address);
- }
- }
- });
- } catch (InvalidJidException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
- }
- }
-
- public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
- Set<AxolotlAddress> addresses = new HashSet<>();
- for(Jid jid : getCryptoTargets(conversation)) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
- }
- }
- }
- }
- } else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
- }
- }
- if (deviceIds.get(account.getJid().toBareJid()) != null) {
- for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
- AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
- }
- }
- }
- }
- }
-
- return addresses;
- }
-
- public boolean createSessionsIfNeeded(final Conversation conversation) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
- boolean newSessions = false;
- Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
- for (AxolotlAddress address : addresses) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
- FetchStatus status = fetchStatusMap.get(address);
- if (status == null || status == FetchStatus.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.PENDING);
- this.buildSessionFromPEP(address);
- newSessions = true;
- } else if (status == FetchStatus.PENDING) {
- newSessions = true;
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
- }
- }
-
- return newSessions;
- }
-
- public boolean trustedSessionVerified(final Conversation conversation) {
- Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
- sessions.addAll(findOwnSessions());
- boolean verified = false;
- for(XmppAxolotlSession session : sessions) {
- if (session.getTrust().trusted()) {
- if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) {
- verified = true;
- } else {
- return false;
- }
- }
- }
- return verified;
- }
-
- public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
- if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- for(Jid jid : jids) {
- AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
- if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- }
- return false;
- }
-
- @Nullable
- private XmppAxolotlMessage buildHeader(Conversation conversation) {
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
- account.getJid().toBareJid(), getOwnDeviceId());
-
- Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
- Set<XmppAxolotlSession> ownSessions = findOwnSessions();
- if (remoteSessions.isEmpty()) {
- return null;
- }
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
- for (XmppAxolotlSession session : remoteSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
- axolotlMessage.addDevice(session);
- }
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
- for (XmppAxolotlSession session : ownSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
- axolotlMessage.addDevice(session);
- }
-
- return axolotlMessage;
- }
+ boolean trustedSessionVerified(Conversation conversation);
@Nullable
- public XmppAxolotlMessage encrypt(Message message) {
- XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
-
- if (axolotlMessage != null) {
- final String content;
- if (message.hasFileOnRemoteHost()) {
- content = message.getFileParams().url.toString();
- } else {
- content = message.getBody();
- }
- try {
- axolotlMessage.encrypt(content);
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
- return null;
- }
- }
-
- return axolotlMessage;
- }
-
- public void preparePayloadMessage(final Message message, final boolean delay) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = encrypt(message);
- if (axolotlMessage == null) {
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
- //mXmppConnectionService.updateConversationUi();
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
- messageCache.put(message.getUuid(), axolotlMessage);
- mXmppConnectionService.resendMessage(message, delay);
- }
- }
- });
- }
+ XmppAxolotlMessage encrypt(Message message);
- public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
- onMessageCreatedCallback.run(axolotlMessage);
- }
- });
- }
+ void preparePayloadMessage(Message message, boolean delay);
- public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
- XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
- if (axolotlMessage != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
- messageCache.remove(message.getUuid());
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
- }
- return axolotlMessage;
- }
+ XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message);
- private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- return (identityKey != null)
- ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
- : null;
- }
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message);
- private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
- AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
- message.getSenderDeviceId());
- XmppAxolotlSession session = sessions.get(senderAddress);
- if (session == null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
- session = recreateUncachedSession(senderAddress);
- if (session == null) {
- session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
- }
- }
- return session;
- }
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message);
- public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
+ boolean fetchMapHasErrors(List<Jid> jids);
- XmppAxolotlSession session = getReceivingSession(message);
- try {
- plaintextMessage = message.decrypt(session, getOwnDeviceId());
- Integer preKeyId = session.getPreKeyId();
- if (preKeyId != null) {
- publishBundlesIfNeeded(false, false);
- session.resetPreKeyId();
- }
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
- }
+ Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid);
- if (session.isFresh() && plaintextMessage != null) {
- putFreshSession(session);
- }
+ Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids);
- return plaintextMessage;
- }
+ long getNumTrustedKeys(Jid jid);
- public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
+ boolean anyTargetHasNoTrustedKeys(List<Jid> jids);
- XmppAxolotlSession session = getReceivingSession(message);
- keyTransportMessage = message.getParameters(session, getOwnDeviceId());
+ boolean isConversationAxolotlCapable(Conversation conversation);
- if (session.isFresh() && keyTransportMessage != null) {
- putFreshSession(session);
- }
+ List<Jid> getCryptoTargets(Conversation conversation);
- return keyTransportMessage;
- }
+ boolean hasPendingKeyFetches(Account account, List<Jid> jids);
- private void putFreshSession(XmppAxolotlSession session) {
- Log.d(Config.LOGTAG,"put fresh session");
- sessions.put(session);
- if (Config.X509_VERIFICATION) {
- if (session.getIdentityKey() != null) {
- verifySessionWithPEP(session);
- } else {
- Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
- }
- }
- }
+ void prepareKeyTransportMessage(Conversation conversation, OnMessageCreatedCallback onMessageCreatedCallback);
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java
new file mode 100644
index 00000000..73974652
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java
@@ -0,0 +1,1041 @@
+package eu.siacs.conversations.crypto.axolotl;
+
+import android.os.Bundle;
+import android.security.KeyChain;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.Pair;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.whispersystems.libaxolotl.AxolotlAddress;
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.IdentityKeyPair;
+import org.whispersystems.libaxolotl.InvalidKeyException;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.SessionBuilder;
+import org.whispersystems.libaxolotl.UntrustedIdentityException;
+import org.whispersystems.libaxolotl.ecc.ECPublicKey;
+import org.whispersystems.libaxolotl.state.PreKeyBundle;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+import org.whispersystems.libaxolotl.util.KeyHelper;
+
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.parser.IqParser;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class AxolotlServiceImpl implements AxolotlService {
+
+ public static final int publishTriesThreshold = 3;
+
+ private final Account account;
+ private final XmppConnectionService mXmppConnectionService;
+ private final SQLiteAxolotlStore axolotlStore;
+ private final SessionMap sessions;
+ private final Map<Jid, Set<Integer>> deviceIds;
+ private final Map<String, XmppAxolotlMessage> messageCache;
+ private final FetchStatusMap fetchStatusMap;
+ private final SerialSingleThreadExecutor executor;
+ private int numPublishTriesOnEmptyPep = 0;
+ private boolean pepBroken = false;
+
+ @Override
+ public void onAdvancedStreamFeaturesAvailable(Account account) {
+ if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) {
+ publishBundlesIfNeeded(true, false);
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
+ }
+ }
+
+ @Override
+ public boolean fetchMapHasErrors(List<Jid> jids) {
+ for(Jid jid : jids) {
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private static class AxolotlAddressMap<T> {
+ protected Map<String, Map<Integer, T>> map;
+ protected final Object MAP_LOCK = new Object();
+
+ public AxolotlAddressMap() {
+ this.map = new HashMap<>();
+ }
+
+ public void put(AxolotlAddress address, T value) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ devices = new HashMap<>();
+ map.put(address.getName(), devices);
+ }
+ devices.put(address.getDeviceId(), value);
+ }
+ }
+
+ public T get(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ return null;
+ }
+ return devices.get(address.getDeviceId());
+ }
+ }
+
+ public Map<Integer, T> getAll(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ return new HashMap<>();
+ }
+ return devices;
+ }
+ }
+
+ public boolean hasAny(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ return devices != null && !devices.isEmpty();
+ }
+ }
+
+ public void clear() {
+ map.clear();
+ }
+
+ }
+
+ private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
+ private final XmppConnectionService xmppConnectionService;
+ private final Account account;
+
+ public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
+ super();
+ this.xmppConnectionService = service;
+ this.account = account;
+ this.fillMap(store);
+ }
+
+ private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
+ IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
+ if(Config.X509_VERIFICATION) {
+ X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
+ if (certificate != null) {
+ Bundle information = CryptoHelper.extractCertificateInformation(certificate);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(bareJid);
+ Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ }
+ }
+ this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
+ }
+ }
+
+ private void fillMap(SQLiteAxolotlStore store) {
+ List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString());
+ putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store);
+ for (Contact contact : account.getRoster().getContacts()) {
+ Jid bareJid = contact.getJid().toBareJid();
+ String address = bareJid.toString();
+ deviceIds = store.getSubDeviceSessions(address);
+ putDevicesForJid(address, deviceIds, store);
+ }
+
+ }
+
+ @Override
+ public void put(AxolotlAddress address, XmppAxolotlSession value) {
+ super.put(address, value);
+ value.setNotFresh();
+ xmppConnectionService.syncRosterToDisk(account);
+ }
+
+ public void put(XmppAxolotlSession session) {
+ this.put(session.getRemoteAddress(), session);
+ }
+ }
+
+ private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
+
+ }
+
+ public static String getLogprefix(Account account) {
+ return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
+ }
+
+ public AxolotlServiceImpl(Account account, XmppConnectionService connectionService) {
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ this.mXmppConnectionService = connectionService;
+ this.account = account;
+ this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
+ this.deviceIds = new HashMap<>();
+ this.messageCache = new HashMap<>();
+ this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
+ this.fetchStatusMap = new FetchStatusMap();
+ this.executor = new SerialSingleThreadExecutor();
+ }
+
+ public String getOwnFingerprint() {
+ return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
+ }
+
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
+ return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
+ return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust);
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
+ Set<IdentityKey> keys = new HashSet<>();
+ for(Jid jid : jids) {
+ keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust));
+ }
+ return keys;
+ }
+
+ @Override
+ public long getNumTrustedKeys(Jid jid) {
+ return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString());
+ }
+
+ @Override
+ public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
+ for(Jid jid : jids) {
+ if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private AxolotlAddress getAddressForJid(Jid jid) {
+ return new AxolotlAddress(jid.toString(), 0);
+ }
+
+ private Set<XmppAxolotlSession> findOwnSessions() {
+ AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
+ return new HashSet<>(this.sessions.getAll(ownAddress).values());
+ }
+
+ private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
+ AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
+ return new HashSet<>(this.sessions.getAll(contactAddress).values());
+ }
+
+ private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
+ HashSet<XmppAxolotlSession> sessions = new HashSet<>();
+ for(Jid jid : conversation.getAcceptedCryptoTargets()) {
+ sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
+ }
+ return sessions;
+ }
+
+ public Set<String> getFingerprintsForOwnSessions() {
+ Set<String> fingerprints = new HashSet<>();
+ for (XmppAxolotlSession session : findOwnSessions()) {
+ fingerprints.add(session.getFingerprint());
+ }
+ return fingerprints;
+ }
+
+ public Set<String> getFingerprintsForContact(final Contact contact) {
+ Set<String> fingerprints = new HashSet<>();
+ for (XmppAxolotlSession session : findSessionsForContact(contact)) {
+ fingerprints.add(session.getFingerprint());
+ }
+ return fingerprints;
+ }
+
+ private boolean hasAny(Jid jid) {
+ return sessions.hasAny(getAddressForJid(jid));
+ }
+
+ public boolean isPepBroken() {
+ return this.pepBroken;
+ }
+
+ public void regenerateKeys(boolean wipeOther) {
+ axolotlStore.regenerate();
+ sessions.clear();
+ fetchStatusMap.clear();
+ publishBundlesIfNeeded(true, wipeOther);
+ }
+
+ public int getOwnDeviceId() {
+ return axolotlStore.getLocalRegistrationId();
+ }
+
+ public Set<Integer> getOwnDeviceIds() {
+ return this.deviceIds.get(account.getJid().toBareJid());
+ }
+
+ private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
+ final XmppAxolotlSession.Trust from,
+ final XmppAxolotlSession.Trust to) {
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+ XmppAxolotlSession session = sessions.get(address);
+ if (session != null && session.getFingerprint() != null
+ && session.getTrust() == from) {
+ session.setTrust(to);
+ }
+ }
+ }
+
+ public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
+ if (jid.toBareJid().equals(account.getJid().toBareJid())) {
+ if (!deviceIds.isEmpty()) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status.");
+ pepBroken = false;
+ numPublishTriesOnEmptyPep = 0;
+ }
+ if (deviceIds.contains(getOwnDeviceId())) {
+ deviceIds.remove(getOwnDeviceId());
+ } else {
+ publishOwnDeviceId(deviceIds);
+ }
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+ if (sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(ownDeviceAddress);
+ }
+ }
+ }
+ Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
+ expiredDevices.removeAll(deviceIds);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED,
+ XmppAxolotlSession.Trust.INACTIVE_TRUSTED);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509,
+ XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED,
+ XmppAxolotlSession.Trust.INACTIVE_UNDECIDED);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED,
+ XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED);
+ Set<Integer> newDevices = new HashSet<>(deviceIds);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED,
+ XmppAxolotlSession.Trust.TRUSTED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509,
+ XmppAxolotlSession.Trust.TRUSTED_X509);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED,
+ XmppAxolotlSession.Trust.UNDECIDED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED,
+ XmppAxolotlSession.Trust.UNTRUSTED);
+ this.deviceIds.put(jid, deviceIds);
+ mXmppConnectionService.keyStatusUpdated(null);
+ }
+
+ public void wipeOtherPepDevices() {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ Set<Integer> deviceIds = new HashSet<>();
+ deviceIds.add(getOwnDeviceId());
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ // TODO: implement this!
+ }
+ });
+ }
+
+ public void purgeKey(final String fingerprint) {
+ axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
+ }
+
+ public void publishOwnDeviceIdIfNeeded() {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
+ } else {
+ Element item = mXmppConnectionService.getIqParser().getItem(packet);
+ Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
+ if (!deviceIds.contains(getOwnDeviceId())) {
+ publishOwnDeviceId(deviceIds);
+ }
+ }
+ }
+ });
+ }
+
+ public void publishOwnDeviceId(Set<Integer> deviceIds) {
+ Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
+ if (!deviceIdsCopy.contains(getOwnDeviceId())) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
+ if (deviceIdsCopy.isEmpty()) {
+ if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
+ pepBroken = true;
+ return;
+ } else {
+ numPublishTriesOnEmptyPep++;
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
+ }
+ } else {
+ numPublishTriesOnEmptyPep = 0;
+ }
+ deviceIdsCopy.add(getOwnDeviceId());
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
+ }
+ }
+ });
+ }
+ }
+
+ public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
+ final Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
+ try {
+ IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
+ PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
+ X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
+ verifier.update(axolotlPublicKey.serialize());
+ byte[] signature = verifier.sign();
+ IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ return; //ignore timeout. do nothing
+ }
+
+ if (packet.getType() == IqPacket.TYPE.ERROR) {
+ Element error = packet.findChild("error");
+ if (error == null || !error.hasChild("item-not-found")) {
+ pepBroken = true;
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
+ return;
+ }
+ }
+
+ PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
+ Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
+ boolean flush = false;
+ if (bundle == null) {
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid bundle:" + packet);
+ bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
+ flush = true;
+ }
+ if (keys == null) {
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid prekeys:" + packet);
+ }
+ try {
+ boolean changed = false;
+ // Validate IdentityKey
+ IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
+ if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
+ changed = true;
+ }
+
+ // Validate signedPreKeyRecord + ID
+ SignedPreKeyRecord signedPreKeyRecord;
+ int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
+ try {
+ signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
+ if (flush
+ || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
+ || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
+ } catch (InvalidKeyIdException e) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
+
+ // Validate PreKeys
+ Set<PreKeyRecord> preKeyRecords = new HashSet<>();
+ if (keys != null) {
+ for (Integer id : keys.keySet()) {
+ try {
+ PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
+ if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
+ preKeyRecords.add(preKeyRecord);
+ }
+ } catch (InvalidKeyIdException ignored) {
+ }
+ }
+ }
+ int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
+ if (newKeys > 0) {
+ List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
+ axolotlStore.getCurrentPreKeyId() + 1, newKeys);
+ preKeyRecords.addAll(newRecords);
+ for (PreKeyRecord record : newRecords) {
+ axolotlStore.storePreKey(record.getId(), record);
+ }
+ changed = true;
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
+ }
+
+
+ if (changed) {
+ if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
+ mXmppConnectionService.publishDisplayName(account);
+ publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ } else {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ }
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announce) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ }
+ } catch (InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
+ Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
+ signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
+ preKeyRecords, getOwnDeviceId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Successfully published bundle. ");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announceAfter) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean isConversationAxolotlCapable(Conversation conversation) {
+ final List<Jid> jids = getCryptoTargets(conversation);
+ for(Jid jid : jids) {
+ if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
+ return false;
+ }
+ }
+ return jids.size() > 0;
+ }
+
+ @Override
+ public List<Jid> getCryptoTargets(Conversation conversation) {
+ final List<Jid> jids;
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ jids = Arrays.asList(conversation.getJid().toBareJid());
+ } else {
+ jids = conversation.getMucOptions().getMembers();
+ jids.remove(account.getJid().toBareJid());
+ }
+ return jids;
+ }
+
+ public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
+ return axolotlStore.getFingerprintTrust(fingerprint);
+ }
+
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return axolotlStore.getFingerprintCertificate(fingerprint);
+ }
+
+ public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
+ axolotlStore.setFingerprintTrust(fingerprint, trust);
+ }
+
+ private void verifySessionWithPEP(final XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
+ final AxolotlAddress address = session.getRemoteAddress();
+ final IdentityKey identityKey = session.getIdentityKey();
+ try {
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
+ if (verification != null) {
+ try {
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initVerify(verification.first[0]);
+ verifier.update(identityKey.serialize());
+ if (verifier.verify(verification.second)) {
+ try {
+ mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
+ String fingerprint = session.getFingerprint();
+ Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
+ setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
+ axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
+ fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
+ Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(address.getName());
+ Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ finishBuildingSessionsFromPEP(address);
+ return;
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG,"could not verify certificate");
+ }
+ }
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
+ }
+ } else {
+ Log.d(Config.LOGTAG,"no verification found");
+ }
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ });
+ } catch (InvalidJidException e) {
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ }
+
+ private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
+ if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
+ && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
+ FetchStatus report = null;
+ if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
+ | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
+ report = FetchStatus.SUCCESS_VERIFIED;
+ } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
+ || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ report = FetchStatus.ERROR;
+ }
+ mXmppConnectionService.keyStatusUpdated(report);
+ }
+ }
+
+ private void buildSessionFromPEP(final AxolotlAddress address) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building new sesstion for " + address.toString());
+ if (address.getDeviceId() == getOwnDeviceId()) {
+ throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
+ }
+
+ try {
+ IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
+ Jid.fromString(address.getName()), address.getDeviceId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
+ mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ fetchStatusMap.put(address, FetchStatus.TIMEOUT);
+ } else if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received preKey IQ packet, processing...");
+ final IqParser parser = mXmppConnectionService.getIqParser();
+ final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
+ final PreKeyBundle bundle = parser.bundle(packet);
+ if (preKeyBundleList.isEmpty() || bundle == null) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ return;
+ }
+ Random random = new Random();
+ final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
+ if (preKey == null) {
+ //should never happen
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ return;
+ }
+
+ final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
+ preKey.getPreKeyId(), preKey.getPreKey(),
+ bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
+ bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
+
+ try {
+ SessionBuilder builder = new SessionBuilder(axolotlStore, address);
+ builder.process(preKeyBundle);
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
+ sessions.put(address, session);
+ if (Config.X509_VERIFICATION) {
+ verifySessionWithPEP(session);
+ } else {
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ } catch (UntrustedIdentityException | InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error building session for " + address + ": "
+ + e.getClass().getName() + ", " + e.getMessage());
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ }
+ } else {
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
+ finishBuildingSessionsFromPEP(address);
+ }
+ }
+ });
+ } catch (InvalidJidException e) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
+ }
+ }
+
+ public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
+ Set<AxolotlAddress> addresses = new HashSet<>();
+ for(Jid jid : getCryptoTargets(conversation)) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Finding devices without session for " + jid);
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
+ }
+ }
+ }
+ }
+ } else {
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Have no target devices in PEP!");
+ }
+ }
+ if (deviceIds.get(account.getJid().toBareJid()) != null) {
+ for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
+ AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
+ }
+ }
+ }
+ }
+ }
+
+ return addresses;
+ }
+
+ public boolean createSessionsIfNeeded(final Conversation conversation) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Creating axolotl sessions if needed...");
+ boolean newSessions = false;
+ Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
+ for (AxolotlAddress address : addresses) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Processing device: " + address.toString());
+ FetchStatus status = fetchStatusMap.get(address);
+ if (status == null || status == FetchStatus.TIMEOUT) {
+ fetchStatusMap.put(address, FetchStatus.PENDING);
+ this.buildSessionFromPEP(address);
+ newSessions = true;
+ } else if (status == FetchStatus.PENDING) {
+ newSessions = true;
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already fetching bundle for " + address.toString());
+ }
+ }
+
+ return newSessions;
+ }
+
+ public boolean trustedSessionVerified(final Conversation conversation) {
+ Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
+ sessions.addAll(findOwnSessions());
+ boolean verified = false;
+ for(XmppAxolotlSession session : sessions) {
+ if (session.getTrust().trusted()) {
+ if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) {
+ verified = true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return verified;
+ }
+
+ @Override
+ public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
+ if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
+ return true;
+ }
+ for(Jid jid : jids) {
+ AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
+ if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private XmppAxolotlMessage buildHeader(Conversation conversation) {
+ final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
+ account.getJid().toBareJid(), getOwnDeviceId());
+
+ Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
+ Set<XmppAxolotlSession> ownSessions = findOwnSessions();
+ if (remoteSessions.isEmpty()) {
+ return null;
+ }
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl foreign keyElements...");
+ for (XmppAxolotlSession session : remoteSessions) {
+ Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl own keyElements...");
+ for (XmppAxolotlSession session : ownSessions) {
+ Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+
+ return axolotlMessage;
+ }
+
+ @Nullable
+ public XmppAxolotlMessage encrypt(Message message) {
+ XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
+
+ if (axolotlMessage != null) {
+ final String content;
+ if (message.hasFileOnRemoteHost()) {
+ content = message.getFileParams().url.toString();
+ } else {
+ content = message.getBody();
+ }
+ try {
+ axolotlMessage.encrypt(content);
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
+ return null;
+ }
+ }
+
+ return axolotlMessage;
+ }
+
+ public void preparePayloadMessage(final Message message, final boolean delay) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = encrypt(message);
+ if (axolotlMessage == null) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
+ //mXmppConnectionService.updateConversationUi();
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
+ messageCache.put(message.getUuid(), axolotlMessage);
+ mXmppConnectionService.resendMessage(message, delay);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
+ onMessageCreatedCallback.run(axolotlMessage);
+ }
+ });
+ }
+
+ public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
+ XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
+ if (axolotlMessage != null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Cache hit: " + message.getUuid());
+ messageCache.remove(message.getUuid());
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Cache miss: " + message.getUuid());
+ }
+ return axolotlMessage;
+ }
+
+ private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ return (identityKey != null)
+ ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
+ : null;
+ }
+
+ private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
+ AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
+ message.getSenderDeviceId());
+ XmppAxolotlSession session = sessions.get(senderAddress);
+ if (session == null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
+ session = recreateUncachedSession(senderAddress);
+ if (session == null) {
+ session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
+ }
+ }
+ return session;
+ }
+
+ public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ try {
+ plaintextMessage = message.decrypt(session, getOwnDeviceId());
+ Integer preKeyId = session.getPreKeyId();
+ if (preKeyId != null) {
+ publishBundlesIfNeeded(false, false);
+ session.resetPreKeyId();
+ }
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
+ }
+
+ if (session.isFresh() && plaintextMessage != null) {
+ putFreshSession(session);
+ }
+
+ return plaintextMessage;
+ }
+
+ public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ keyTransportMessage = message.getParameters(session, getOwnDeviceId());
+
+ if (session.isFresh() && keyTransportMessage != null) {
+ putFreshSession(session);
+ }
+
+ return keyTransportMessage;
+ }
+
+ private void putFreshSession(XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG,"put fresh session");
+ sessions.put(session);
+ if (Config.X509_VERIFICATION) {
+ if (session.getIdentityKey() != null) {
+ verifySessionWithPEP(session);
+ } else {
+ Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java
new file mode 100644
index 00000000..b0228d34
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java
@@ -0,0 +1,207 @@
+package eu.siacs.conversations.crypto.axolotl;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.whispersystems.libaxolotl.AxolotlAddress;
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+/**
+ * Axolotl Service Stub implementation to avoid axolotl usage.
+ */
+public class AxolotlServiceStub implements AxolotlService {
+
+ @Override
+ public String getOwnFingerprint() {
+ return null;
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getFingerprintsForOwnSessions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getFingerprintsForContact(Contact contact) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isPepBroken() {
+ return true;
+ }
+
+ @Override
+ public void regenerateKeys(boolean wipeOther) {
+
+ }
+
+ @Override
+ public int getOwnDeviceId() {
+ return 0;
+ }
+
+ @Override
+ public Set<Integer> getOwnDeviceIds() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void registerDevices(Jid jid, @NonNull Set<Integer> deviceIds) {
+
+ }
+
+ @Override
+ public void wipeOtherPepDevices() {
+
+ }
+
+ @Override
+ public void purgeKey(String fingerprint) {
+
+ }
+
+ @Override
+ public void publishOwnDeviceIdIfNeeded() {
+
+ }
+
+ @Override
+ public void publishOwnDeviceId(Set<Integer> deviceIds) {
+
+ }
+
+ @Override
+ public void publishDeviceVerificationAndBundle(SignedPreKeyRecord signedPreKeyRecord, Set<PreKeyRecord> preKeyRecords, boolean announceAfter, boolean wipe) {
+
+ }
+
+ @Override
+ public void publishBundlesIfNeeded(boolean announce, boolean wipe) {
+
+ }
+
+ @Override
+ public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
+ return XmppAxolotlSession.Trust.TRUSTED;
+ }
+
+ @Override
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return null;
+ }
+
+ @Override
+ public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
+
+ }
+
+ @Override
+ public Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean createSessionsIfNeeded(Conversation conversation) {
+ return false;
+ }
+
+ @Override
+ public boolean trustedSessionVerified(Conversation conversation) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public XmppAxolotlMessage encrypt(Message message) {
+ return null;
+ }
+
+ @Override
+ public void preparePayloadMessage(Message message, boolean delay) {
+
+ }
+
+ @Override
+ public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
+ return null;
+ }
+
+ @Override
+ public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
+ return null;
+ }
+
+ @Override
+ public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
+ return null;
+ }
+
+ @Override
+ public boolean fetchMapHasErrors(List<Jid> jids) {
+ return false;
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public long getNumTrustedKeys(Jid jid) {
+ return 0;
+ }
+
+ @Override
+ public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
+ return false;
+ }
+
+ @Override
+ public boolean isConversationAxolotlCapable(Conversation conversation) {
+ return false;
+ }
+
+ @Override
+ public List<Jid> getCryptoTargets(Conversation conversation) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
+ return false;
+ }
+
+ @Override
+ public void prepareKeyTransportMessage(Conversation conversation, OnMessageCreatedCallback onMessageCreatedCallback) {
+
+ }
+
+ @Override
+ public void onAdvancedStreamFeaturesAvailable(Account account) {
+
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
index 3c8cb3c1..c634d877 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
@@ -77,7 +77,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
this.localRegistrationId = loadRegistrationId();
this.currentPreKeyId = loadCurrentPreKeyId();
for (SignedPreKeyRecord record : loadSignedPreKeys()) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
}
}
@@ -95,7 +95,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (ownKey != null) {
return ownKey;
} else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
ownKey = generateIdentityKeyPair();
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
}
@@ -112,13 +112,13 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (!regenerate && regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
reg_id = generateRegistrationId();
boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to write new key to the database!");
}
}
return reg_id;
@@ -130,7 +130,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
reg_id = 0;
}
return reg_id;
@@ -344,7 +344,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to write new prekey id to the database!");
}
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
index b713eb5f..93ed32a2 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -168,10 +168,10 @@ public class XmppAxolotlSession {
try {
try {
PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
IdentityKey msgIdentityKey = message.getIdentityKey();
if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
} else {
this.identityKey = msgIdentityKey;
plaintext = cipher.decrypt(message);
@@ -180,14 +180,14 @@ public class XmppAxolotlSession {
}
}
} catch (InvalidMessageException | InvalidVersionException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "WhisperMessage received");
WhisperMessage message = new WhisperMessage(encryptedKey);
plaintext = cipher.decrypt(message);
} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
}
} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
}
if (plaintext != null) {
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
index df92898c..8fd91cf4 100644
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
+++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.crypto.sasl;
import android.util.Base64;
+
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index 356b34e5..7aee78b5 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -5,7 +5,6 @@ import android.database.Cursor;
import android.os.SystemClock;
import android.util.Pair;
-import eu.siacs.conversations.crypto.PgpDecryptionService;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
@@ -20,10 +19,14 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.OtrService;
+import eu.siacs.conversations.crypto.PgpDecryptionService;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceStub;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -165,6 +168,10 @@ public class Account extends AbstractEntity {
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
+ public Account() {
+ this.uuid = "0";
+ }
+
public Account(final Jid jid, final String password) {
this(java.util.UUID.randomUUID().toString(), jid,
password, 0, null, "", null, null, null, 5222);
@@ -251,10 +258,6 @@ public class Account extends AbstractEntity {
return this.hostname == null ? "" : this.hostname;
}
- public boolean isOnion() {
- return getServer().toString().toLowerCase().endsWith(".onion");
- }
-
public void setPort(int port) {
this.port = port;
}
@@ -352,10 +355,15 @@ public class Account extends AbstractEntity {
public void initAccountServices(final XmppConnectionService context) {
this.mOtrService = new OtrService(context, this);
- this.axolotlService = new AxolotlService(this, context);
- if (xmppConnection != null) {
- xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
- }
+ if (ConversationsPlusPreferences.omemoEnabled()) {
+ this.axolotlService = new AxolotlServiceImpl(this, context);
+ if (xmppConnection != null) {
+ xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
+ }
+ } else {
+ this.axolotlService = new AxolotlServiceStub();
+ }
+
this.pgpDecryptionService = new PgpDecryptionService(context);
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
index 088dfd8a..fa30443d 100644
--- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java
+++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.entities;
+import android.graphics.Color;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -87,6 +89,11 @@ public class Bookmark extends Element implements ListItem {
return tags;
}
+ @Override
+ public int getStatusColor() {
+ return Color.parseColor("#259B23");
+ }
+
public String getNick() {
return this.findChildContent("nick");
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 691fc3e4..bdde12e3 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
+import android.graphics.Color;
import org.json.JSONArray;
import org.json.JSONException;
@@ -167,11 +168,16 @@ public class Contact implements ListItem, Blockable {
return tags;
}
+ @Override
+ public int getStatusColor() {
+ return Color.parseColor(UIHelper.getStatusColor(getMostAvailableStatus()));
+ }
+
public boolean match(String needle) {
if (needle == null || needle.isEmpty()) {
return true;
}
- needle = needle.toLowerCase(Locale.US).trim();
+ needle = needle.toLowerCase(Locale.US);
String[] parts = needle.split("\\s+");
if (parts.length > 1) {
for(int i = 0; i < parts.length; ++i) {
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 0252ea74..62f976b3 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -21,6 +21,8 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
@@ -259,16 +261,12 @@ public class Conversation extends AbstractEntity implements Blockable {
return null;
}
+ // TODO Check if this is really necessary
public void populateWithMessages(final List<Message> messages) {
synchronized (this.messages) {
messages.clear();
messages.addAll(this.messages);
}
- for(Iterator<Message> iterator = messages.iterator(); iterator.hasNext();) {
- if (iterator.next().wasMergedIntoPrevious()) {
- iterator.remove();
- }
- }
}
@Override
@@ -308,14 +306,6 @@ public class Conversation extends AbstractEntity implements Blockable {
return this.mFirstMamReference;
}
- public void setLastClearHistory(long time) {
- setAttribute("last_clear_history",String.valueOf(time));
- }
-
- public long getLastClearHistory() {
- return getLongAttribute("last_clear_history", 0);
- }
-
public List<Jid> getAcceptedCryptoTargets() {
if (mode == MODE_SINGLE) {
return Arrays.asList(getJid().toBareJid());
@@ -769,7 +759,7 @@ public class Conversation extends AbstractEntity implements Blockable {
if (message.hasFileOnRemoteHost()) {
otherBody = message.getFileParams().url.toString();
} else {
- otherBody = message.body;
+ otherBody = message.getBody();
}
if (otherBody != null && otherBody.equals(body)) {
return message;
@@ -781,10 +771,6 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public long getLastMessageTransmitted() {
- long last_clear = getLastClearHistory();
- if (last_clear != 0) {
- return last_clear;
- }
synchronized (this.messages) {
for(int i = this.messages.size() - 1; i >= 0; --i) {
Message message = this.messages.get(i);
@@ -941,13 +927,19 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public int unreadCount() {
+ if (getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) {
+ return 0;
+ }
synchronized (this.messages) {
int count = 0;
for(int i = this.messages.size() - 1; i >= 0; --i) {
- if (this.messages.get(i).isRead()) {
+ Message message = this.messages.get(i);
+ if (message.isRead()) {
return count;
}
- ++count;
+ if (alwaysNotify() || MessageUtil.wasHighlightedOrPrivate(message)) {
+ ++count;
+ }
}
return count;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java
index 22aedd4b..56804fbf 100644
--- a/src/main/java/eu/siacs/conversations/entities/ListItem.java
+++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java
@@ -11,6 +11,8 @@ public interface ListItem extends Comparable<ListItem> {
Jid getJid();
+ public int getStatusColor();
+
List<Tag> getTags();
final class Tag {
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index be9d3d92..5fafb6c8 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -5,13 +5,10 @@ import android.database.Cursor;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.Arrays;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
-import eu.siacs.conversations.utils.GeoHelper;
+import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.MimeUtils;
-import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -66,7 +63,7 @@ public class Message extends AbstractEntity {
protected String conversationUuid;
protected Jid counterpart;
protected Jid trueCounterpart;
- protected String body;
+ private String body;
protected String encryptedBody;
protected long timeSent;
protected int encryption;
@@ -84,6 +81,7 @@ public class Message extends AbstractEntity {
private Message mNextMessage = null;
private Message mPreviousMessage = null;
private String axolotlFingerprint = null;
+ private Decision mTreatAsDownloadAble = Decision.NOT_DECIDED;
private Message() {
@@ -189,14 +187,6 @@ public class Message extends AbstractEntity {
return message;
}
- public static Message createLoadMoreMessage(Conversation conversation) {
- final Message message = new Message();
- message.setType(Message.TYPE_STATUS);
- message.setConversation(conversation);
- message.setBody("LOAD_MORE");
- return message;
- }
-
@Override
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
@@ -353,10 +343,6 @@ public class Message extends AbstractEntity {
this.carbon = carbon;
}
- public void setEdited(String edited) {
- this.edited = edited;
- }
-
public boolean edited() {
return this.edited != null;
}
@@ -365,10 +351,6 @@ public class Message extends AbstractEntity {
this.trueCounterpart = trueCounterpart;
}
- public Jid getTrueCounterpart() {
- return this.trueCounterpart;
- }
-
public Transferable getTransferable() {
return this.transferable;
}
@@ -378,30 +360,40 @@ public class Message extends AbstractEntity {
}
public boolean equals(Message message) {
- if (this.serverMsgId != null && message.getServerMsgId() != null) {
- return this.serverMsgId.equals(message.getServerMsgId());
- } else if (this.body == null || this.counterpart == null) {
+ if (this.getServerMsgId() != null && message.getServerMsgId() != null) {
+ return this.getServerMsgId().equals(message.getServerMsgId());
+ } else if (this.getBody() == null || this.getCounterpart() == null
+ || message.getBody() == null || message.getCounterpart() == null) {
return false;
} else {
String body, otherBody;
if (this.hasFileOnRemoteHost()) {
- body = getFileParams().url.toString();
- otherBody = message.body == null ? null : message.body.trim();
+ body = this.getFileParams().url.toString();
} else {
- body = this.body;
- otherBody = message.body;
+ body = this.getBody();
}
- if (message.getRemoteMsgId() != null) {
- return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
- && this.counterpart.equals(message.getCounterpart())
+ if (message.hasFileOnRemoteHost()) {
+ otherBody = message.getFileParams().url.toString();
+ } else {
+ otherBody = message.getBody();
+ }
+
+ if (message.getRemoteMsgId() != null && this.getRemoteMsgId() != null) {
+ return (message.getRemoteMsgId().equals(this.getRemoteMsgId())
+ || message.getRemoteMsgId().equals(this.getUuid())
+ || message.getUuid().equals(this.getRemoteMsgId()))
+ && this.getCounterpart().equals(message.getCounterpart())
&& (body.equals(otherBody)
||(message.getEncryption() == Message.ENCRYPTION_PGP
&& message.getRemoteMsgId().matches("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"))) ;
} else {
- return this.remoteMsgId == null
- && this.counterpart.equals(message.getCounterpart())
- && body.equals(otherBody)
- && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
+ // existing (send) message with no remoteMsgId and MAM message with remoteMsgId
+ return ((this.getRemoteMsgId() == null && message.getRemoteMsgId() != null)
+ || (this.getRemoteMsgId() != null && message.getRemoteMsgId() == null)
+ // both null is also acceptable
+ || (this.getRemoteMsgId() == null && message.getRemoteMsgId() == null))
+ && this.getCounterpart().equals(message.getCounterpart())
+ && body.equals(otherBody);
}
}
}
@@ -434,97 +426,16 @@ public class Message extends AbstractEntity {
}
}
- public boolean isLastCorrectableMessage() {
- Message next = next();
- while(next != null) {
- if (next.isCorrectable()) {
- return false;
- }
- next = next.next();
- }
- return isCorrectable();
- }
-
- private boolean isCorrectable() {
- return getStatus() != STATUS_RECEIVED && !isCarbon();
- }
-
- public boolean mergeable(final Message message) {
- return message != null &&
- (message.getType() == Message.TYPE_TEXT &&
- this.getTransferable() == null &&
- message.getTransferable() == null &&
- message.getEncryption() != Message.ENCRYPTION_PGP &&
- message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED &&
- this.getType() == message.getType() &&
- //this.getStatus() == message.getStatus() &&
- isStatusMergeable(this.getStatus(), message.getStatus()) &&
- this.getEncryption() == message.getEncryption() &&
- this.getCounterpart() != null &&
- this.getCounterpart().equals(message.getCounterpart()) &&
- this.edited() == message.edited() &&
- (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
- !GeoHelper.isGeoUri(message.getBody()) &&
- !GeoHelper.isGeoUri(this.body) &&
- message.treatAsDownloadable() == Decision.NEVER &&
- this.treatAsDownloadable() == Decision.NEVER &&
- !message.getBody().startsWith(ME_COMMAND) &&
- !this.getBody().startsWith(ME_COMMAND) &&
- !this.bodyIsHeart() &&
- !message.bodyIsHeart() &&
- this.isTrusted() == message.isTrusted()
- );
- }
-
- private static boolean isStatusMergeable(int a, int b) {
- return a == b || (
- (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
- || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
- || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
- || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
- || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
- || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
- );
- }
-
- public String getMergedBody() {
- StringBuilder body = new StringBuilder(this.body.trim());
- Message current = this;
- while(current.mergeable(current.next())) {
- current = current.next();
- body.append(MERGE_SEPARATOR);
- body.append(current.getBody().trim());
- }
- return body.toString();
- }
-
public boolean hasMeCommand() {
- return getMergedBody().startsWith(ME_COMMAND);
+ return getBody().startsWith(ME_COMMAND);
}
- public int getMergedStatus() {
- int status = this.status;
- Message current = this;
- while(current.mergeable(current.next())) {
- current = current.next();
- status = current.status;
- }
- return status;
- }
-
- public long getMergedTimeSent() {
- long time = this.timeSent;
- Message current = this;
- while(current.mergeable(current.next())) {
- current = current.next();
- time = current.timeSent;
+ public String getBodyReplacedMeCommand(String replaceString) {
+ try {
+ return getBody().replaceAll("^" + Message.ME_COMMAND, replaceString + " ");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return getBody();
}
- return time;
- }
-
- public boolean wasMergedIntoPrevious() {
- Message prev = this.prev();
- return prev != null && prev.mergeable(this);
}
public boolean trusted() {
@@ -568,28 +479,33 @@ public class Message extends AbstractEntity {
MUST,
SHOULD,
NEVER,
+ NOT_DECIDED,
}
- private static String extractRelevantExtension(URL url) {
+ private String extractRelevantExtension(URL url) {
+ if (url == null) {
+ return null;
+ }
String path = url.getPath();
return extractRelevantExtension(path);
}
- private static String extractRelevantExtension(String path) {
+ private String extractRelevantExtension(String path) {
if (path == null || path.isEmpty()) {
return null;
}
String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
- int dotPosition = filename.lastIndexOf(".");
- if (dotPosition != -1) {
- String extension = filename.substring(dotPosition + 1);
+ final String lastPart = FileUtils.getLastExtension(filename);
+
+ if (!lastPart.isEmpty()) {
// we want the real file extension, not the crypto one
- if (Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
- return extractRelevantExtension(filename.substring(0,dotPosition));
+ final String secondToLastPart = FileUtils.getSecondToLastExtension(filename);
+ if (!secondToLastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
+ return secondToLastPart;
} else {
- return extension;
+ return lastPart;
}
}
return null;
@@ -605,53 +521,74 @@ public class Message extends AbstractEntity {
}
} else {
try {
- return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
+ return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(this.getBody())));
} catch (MalformedURLException e) {
return null;
}
}
}
+ /**
+ * in case of later found error with decision, set it to a value which does not affect anything, hopefully
+ */
+ public void setNoDownloadable() {
+ mTreatAsDownloadAble = Decision.NEVER;
+ }
+
public Decision treatAsDownloadable() {
- if (body.trim().contains(" ")) {
- return Decision.NEVER;
+ // only test this ones, body will not change
+ if (mTreatAsDownloadAble != Decision.NOT_DECIDED) {
+ return mTreatAsDownloadAble;
+ }
+ /**
+ * there are a few cases where spaces result in an unwanted behavior, e.g.
+ * "http://example.com/image.jpg" text that will not be shown /abc.png"
+ * or more than one image link in one message.
+ */
+ if (getBody().contains(" ")) {
+ mTreatAsDownloadAble = Decision.NEVER;
+ return mTreatAsDownloadAble;
}
try {
URL url = new URL(body);
if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
- return Decision.NEVER;
+ mTreatAsDownloadAble = Decision.NEVER;
+ return mTreatAsDownloadAble;
} else if (oob) {
- return Decision.MUST;
+ mTreatAsDownloadAble = Decision.MUST;
+ return mTreatAsDownloadAble;
}
String extension = extractRelevantExtension(url);
if (extension == null) {
- return Decision.NEVER;
+ mTreatAsDownloadAble = Decision.NEVER;
+ return mTreatAsDownloadAble;
}
String ref = url.getRef();
boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}");
if (encrypted) {
if (MimeUtils.guessMimeTypeFromExtension(extension) != null) {
- return Decision.MUST;
+ mTreatAsDownloadAble = Decision.MUST;
+ return mTreatAsDownloadAble;
} else {
- return Decision.NEVER;
+ mTreatAsDownloadAble = Decision.NEVER;
+ return mTreatAsDownloadAble;
}
} else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
|| Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
- return Decision.SHOULD;
+ mTreatAsDownloadAble = Decision.SHOULD;
+ return mTreatAsDownloadAble;
} else {
- return Decision.NEVER;
+ mTreatAsDownloadAble = Decision.NEVER;
+ return mTreatAsDownloadAble;
}
} catch (MalformedURLException e) {
- return Decision.NEVER;
+ mTreatAsDownloadAble = Decision.NEVER;
+ return mTreatAsDownloadAble;
}
}
- public boolean bodyIsHeart() {
- return body != null && UIHelper.HEARTS.contains(body.trim());
- }
-
public FileParams getFileParams() {
FileParams params = getLegacyFileParams();
if (params != null) {
@@ -661,10 +598,10 @@ public class Message extends AbstractEntity {
if (this.transferable != null) {
params.size = this.transferable.getFileSize();
}
- if (body == null) {
+ if (this.getBody() == null) {
return params;
}
- String parts[] = body.split("\\|");
+ String parts[] = this.getBody().split("\\|");
switch (parts.length) {
case 1:
try {
@@ -723,10 +660,10 @@ public class Message extends AbstractEntity {
public FileParams getLegacyFileParams() {
FileParams params = new FileParams();
- if (body == null) {
+ if (this.getBody() == null) {
return params;
}
- String parts[] = body.split(",");
+ String parts[] = this.getBody().split(",");
if (parts.length == 3) {
try {
params.size = Long.parseLong(parts[0]);
diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
index 42f2d840..0e041454 100644
--- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
+++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
@@ -159,7 +159,7 @@ public class ServiceDiscoveryResult {
}
public String getVer() {
- return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
+ return new String(Base64.encode(this.ver, Base64.DEFAULT));
}
public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
index 05fa0b82..649f767d 100644
--- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
@@ -12,11 +12,9 @@ import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.PhoneHelper;
-import eu.siacs.conversations.xmpp.jid.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public abstract class AbstractGenerator {
private final String[] FEATURES = {
@@ -38,35 +36,13 @@ public abstract class AbstractGenerator {
"urn:xmpp:chat-markers:0",
"urn:xmpp:receipts"
};
- private final String[] MESSAGE_CORRECTION_FEATURES = {
- "urn:xmpp:message-correct:0"
- };
- private String mVersion = null;
- protected final String IDENTITY_NAME = "Conversations";
protected final String IDENTITY_TYPE = "phone";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
- protected XmppConnectionService mXmppConnectionService;
-
- protected AbstractGenerator(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- protected String getIdentityVersion() {
- if (mVersion == null) {
- this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService);
- }
- return this.mVersion;
- }
-
- public String getIdentityName() {
- return IDENTITY_NAME + " " + getIdentityVersion();
- }
-
public String getCapHash() {
StringBuilder s = new StringBuilder();
- s.append("client/" + IDENTITY_TYPE + "//" + getIdentityName() + "<");
+ s.append("client/" + IDENTITY_TYPE + "//" + ConversationsPlusApplication.getNameAndVersion() + "<");
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
@@ -78,7 +54,7 @@ public abstract class AbstractGenerator {
s.append(feature + "<");
}
byte[] sha1 = md.digest(s.toString().getBytes());
- return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
+ return new String(Base64.encode(sha1, Base64.DEFAULT));
}
public static String getTimestamp(long time) {
@@ -89,12 +65,9 @@ public abstract class AbstractGenerator {
public List<String> getFeatures() {
ArrayList<String> features = new ArrayList<>();
features.addAll(Arrays.asList(FEATURES));
- if (mXmppConnectionService.confirmMessages()) {
+ if (Settings.CONFIRM_MESSAGE_RECEIVED) {
features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES));
}
- if (mXmppConnectionService.allowMessageCorrection()) {
- features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES));
- }
Collections.sort(features);
return features;
}
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index b7911ef7..eff9d9c0 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -15,13 +15,13 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import eu.siacs.conversations.Config;
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.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.forms.Data;
@@ -31,10 +31,6 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class IqGenerator extends AbstractGenerator {
- public IqGenerator(final XmppConnectionService service) {
- super(service);
- }
-
public IqPacket discoResponse(final IqPacket request) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
packet.setId(request.getId());
@@ -45,7 +41,7 @@ public class IqGenerator extends AbstractGenerator {
final Element identity = query.addChild("identity");
identity.setAttribute("category", "client");
identity.setAttribute("type", IDENTITY_TYPE);
- identity.setAttribute("name", getIdentityName());
+ identity.setAttribute("name", ConversationsPlusApplication.getNameAndVersion());
for (final String feature : getFeatures()) {
query.addChild("feature").setAttribute("var", feature);
}
@@ -55,8 +51,8 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket versionResponse(final IqPacket request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
Element query = packet.query("jabber:iq:version");
- query.addChild("name").setContent(IDENTITY_NAME);
- query.addChild("version").setContent(getIdentityVersion());
+ query.addChild("name").setContent(ConversationsPlusApplication.getName());
+ query.addChild("version").setContent(ConversationsPlusApplication.getVersion());
return packet;
}
@@ -88,51 +84,13 @@ public class IqGenerator extends AbstractGenerator {
return publish("http://jabber.org/protocol/nick", item);
}
- public IqPacket publishAvatar(Avatar avatar) {
- final Element item = new Element("item");
- item.setAttribute("id", avatar.sha1sum);
- final Element data = item.addChild("data", "urn:xmpp:avatar:data");
- data.setContent(avatar.image);
- return publish("urn:xmpp:avatar:data", item);
- }
-
- public IqPacket publishAvatarMetadata(final Avatar avatar) {
- final Element item = new Element("item");
- item.setAttribute("id", avatar.sha1sum);
- final Element metadata = item
- .addChild("metadata", "urn:xmpp:avatar:metadata");
- final Element info = metadata.addChild("info");
- info.setAttribute("bytes", avatar.size);
- info.setAttribute("id", avatar.sha1sum);
- info.setAttribute("height", avatar.height);
- info.setAttribute("width", avatar.height);
- info.setAttribute("type", avatar.type);
- return publish("urn:xmpp:avatar:metadata", item);
- }
-
- public IqPacket retrievePepAvatar(final Avatar avatar) {
- final Element item = new Element("item");
- item.setAttribute("id", avatar.sha1sum);
- final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
- packet.setTo(avatar.owner);
- return packet;
- }
-
- public IqPacket retrieveVcardAvatar(final Avatar avatar) {
+ public static IqPacket retrieveVcardAvatar(final Avatar avatar) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(avatar.owner);
packet.addChild("vCard", "vcard-temp");
return packet;
}
- public IqPacket retrieveAvatarMetaData(final Jid to) {
- final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
- if (to != null) {
- packet.setTo(to);
- }
- return packet;
- }
-
public IqPacket retrieveDeviceIds(final Jid to) {
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
if(to != null) {
diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
index 0e7a8ce6..2d7b66b5 100644
--- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
@@ -9,20 +9,17 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
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;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class MessageGenerator extends AbstractGenerator {
- public MessageGenerator(XmppConnectionService service) {
- super(service);
- }
private MessagePacket preparePacket(Message message) {
Conversation conversation = message.getConversation();
@@ -32,13 +29,13 @@ public class MessageGenerator extends AbstractGenerator {
packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT);
packet.addChild("markable", "urn:xmpp:chat-markers:0");
- if (this.mXmppConnectionService.indicateReceived()) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
packet.addChild("request", "urn:xmpp:receipts");
}
} else if (message.getType() == Message.TYPE_PRIVATE) {
packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT);
- if (this.mXmppConnectionService.indicateReceived()) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
packet.addChild("request", "urn:xmpp:receipts");
}
} else {
@@ -72,6 +69,15 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
+ public static void addXhtmlImImage(MessagePacket packet, Message.FileParams params) {
+ Element html = packet.addChild("html", "http://jabber.org/protocol/xhtml-im");
+ Element body = html.addChild("body", "http://www.w3.org/1999/xhtml");
+ Element img = body.addChild("img");
+ img.setAttribute("src", params.url.toString());
+ img.setAttribute("height", params.height);
+ img.setAttribute("width", params.width);
+ }
+
public static void addMessageHints(MessagePacket packet) {
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
@@ -107,6 +113,9 @@ public class MessageGenerator extends AbstractGenerator {
Message.FileParams fileParams = message.getFileParams();
content = fileParams.url.toString();
packet.addChild("x","jabber:x:oob").addChild("url").setContent(content);
+ if (fileParams.width > 0 && fileParams.height > 0) {
+ addXhtmlImImage(packet,fileParams);
+ }
} else {
content = message.getBody();
}
@@ -142,9 +151,8 @@ public class MessageGenerator extends AbstractGenerator {
packet.setType(MessagePacket.TYPE_CHAT);
packet.setTo(to);
packet.setFrom(account.getJid());
- Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0");
+ Element received = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
received.setAttribute("id", id);
- packet.addChild("store", "urn:xmpp:hints");
return packet;
}
diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
index 093a8963..9ac7d318 100644
--- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
@@ -3,16 +3,11 @@ package eu.siacs.conversations.generator;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Presence;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
public class PresenceGenerator extends AbstractGenerator {
- public PresenceGenerator(XmppConnectionService service) {
- super(service);
- }
-
private PresencePacket subscription(String type, Contact contact) {
PresencePacket packet = new PresencePacket();
packet.setAttribute("type", type);
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
index a8b31a7a..f105646f 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
@@ -1,7 +1,5 @@
package eu.siacs.conversations.http;
-import android.os.Build;
-
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import java.io.IOException;
diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index d23cb71a..66687c3a 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
@@ -15,6 +15,12 @@ import java.util.concurrent.CancellationException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.exceptions.RemoteFileNotFoundException;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
@@ -25,6 +31,7 @@ import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.FileUtils;
public class HttpDownloadConnection implements Transferable {
@@ -37,13 +44,11 @@ public class HttpDownloadConnection implements Transferable {
private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
- private boolean mUseTor = false;
- private boolean canceled = false;
+ private boolean canceled = false;
public HttpDownloadConnection(HttpConnectionManager manager) {
this.mHttpConnectionManager = manager;
this.mXmppConnectionService = manager.getXmppConnectionService();
- this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@Override
@@ -73,23 +78,23 @@ public class HttpDownloadConnection implements Transferable {
} else {
mUrl = new URL(message.getBody());
}
- String[] parts = mUrl.getPath().toLowerCase().split("\\.");
- String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null;
- String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null;
- if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
+ final String sUrlFilename = mUrl.getPath().substring(mUrl.getPath().lastIndexOf('/')).toLowerCase();
+ final String lastPart = FileUtils.getLastExtension(sUrlFilename);
+
+ if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) {
this.message.setEncryption(Message.ENCRYPTION_PGP);
} else if (message.getEncryption() != Message.ENCRYPTION_OTR
&& message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
this.message.setEncryption(Message.ENCRYPTION_NONE);
}
String extension;
- if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
- extension = secondToLast;
+ if (!lastPart.isEmpty() && VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
+ extension = FileUtils.getSecondToLastExtension(sUrlFilename);
} else {
extension = lastPart;
}
message.setRelativeFilePath(message.getUuid() + "." + extension);
- this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
String reference = mUrl.getRef();
if (reference != null && reference.length() == 96) {
this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
@@ -123,7 +128,7 @@ public class HttpDownloadConnection implements Transferable {
}
private void finish() {
- mXmppConnectionService.getFileBackend().updateMediaScanner(file);
+ FileBackend.updateMediaScanner(file, mXmppConnectionService);
message.setTransferable(null);
mHttpConnectionManager.finishConnection(this);
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
@@ -169,6 +174,10 @@ public class HttpDownloadConnection implements Transferable {
HttpDownloadConnection.this.acceptedAutomatically = false;
HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
return;
+ } catch (RemoteFileNotFoundException e) {
+ message.setNoDownloadable();
+ cancel();
+ return;
} catch (IOException e) {
Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
if (interactive) {
@@ -178,7 +187,10 @@ public class HttpDownloadConnection implements Transferable {
return;
}
file.setExpectedSize(size);
- if (mHttpConnectionManager.hasStoragePermission() && size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
+ if (mHttpConnectionManager.hasStoragePermission()
+ && size != -1
+ && size <= ConversationsPlusPreferences.autoAcceptFileSize()
+ && mXmppConnectionService.isDownloadAllowedInConnection()) {
HttpDownloadConnection.this.acceptedAutomatically = true;
new Thread(new FileDownloader(interactive)).start();
} else {
@@ -189,34 +201,37 @@ public class HttpDownloadConnection implements Transferable {
}
private long retrieveFileSize() throws IOException {
- try {
- Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
+ try {
+ Logging.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
changeStatus(STATUS_CHECKING);
- HttpURLConnection connection;
- if (mUseTor) {
- connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
- } else {
- connection = (HttpURLConnection) mUrl.openConnection();
- }
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
connection.setRequestMethod("HEAD");
- Log.d(Config.LOGTAG,"url: "+connection.getURL().toString());
- Log.d(Config.LOGTAG,"connection: "+connection.toString());
- connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
+ Logging.d(Config.LOGTAG, "url: " + connection.getURL().toString());
+ Logging.d(Config.LOGTAG, "connection: " + connection.toString());
+ connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
+ // https://code.google.com/p/android/issues/detail?id=24672
+ connection.setRequestProperty("Accept-Encoding", "");
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
}
connection.connect();
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ Logging.d(Config.LOGTAG, "remote file not found");
+ throw new RemoteFileNotFoundException();
+ }
String contentLength = connection.getHeaderField("Content-Length");
connection.disconnect();
if (contentLength == null) {
- throw new IOException();
+ return -1;
}
return Long.parseLong(contentLength, 10);
- } catch (IOException e) {
+ } catch (RemoteFileNotFoundException e) {
throw e;
- } catch (NumberFormatException e) {
- throw new IOException();
- }
+ } catch (IOException e) {
+ return -1;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
}
}
@@ -248,71 +263,66 @@ public class HttpDownloadConnection implements Transferable {
}
}
- private void download() throws Exception {
- InputStream is = null;
- PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
- try {
- wakeLock.acquire();
- HttpURLConnection connection;
- if (mUseTor) {
- connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
- } else {
- connection = (HttpURLConnection) mUrl.openConnection();
- }
- if (connection instanceof HttpsURLConnection) {
- mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
- }
- connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
- final boolean tryResume = file.exists() && file.getKey() == null;
- if (tryResume) {
- Log.d(Config.LOGTAG,"http download trying resume");
- long size = file.getSize();
- connection.setRequestProperty("Range", "bytes="+size+"-");
- }
- connection.connect();
- is = new BufferedInputStream(connection.getInputStream());
- boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges"));
- long transmitted = 0;
- long expected = file.getExpectedSize();
- if (tryResume && serverResumed) {
- Log.d(Config.LOGTAG,"server resumed");
- transmitted = file.getSize();
- updateProgress((int) ((((double) transmitted) / expected) * 100));
- os = AbstractConnectionManager.createAppendedOutputStream(file);
- } else {
- file.getParentFile().mkdirs();
- file.createNewFile();
- os = AbstractConnectionManager.createOutputStream(file, true);
- }
- int count = -1;
- byte[] buffer = new byte[1024];
- while ((count = is.read(buffer)) != -1) {
- transmitted += count;
- os.write(buffer, 0, count);
- updateProgress((int) ((((double) transmitted) / expected) * 100));
- if (canceled) {
- throw new CancellationException();
- }
- }
- } catch (CancellationException | IOException e) {
- throw e;
- } finally {
- if (os != null) {
- try {
- os.flush();
- } catch (final IOException ignored) {
-
- }
- }
- FileBackend.close(os);
- FileBackend.close(is);
- wakeLock.release();
- }
+ private void download() throws SSLHandshakeException, IOException {
+ InputStream is = null;
+ PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
+ try {
+ wakeLock.acquire();
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ if (connection instanceof HttpsURLConnection) {
+ mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
+ }
+ connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
+ final boolean tryResume = file.exists() && file.getKey() == null;
+ if (tryResume) {
+ Logging.d(Config.LOGTAG, "http download trying resume");
+ long size = file.getSize();
+ connection.setRequestProperty("Range", "bytes="+size+"-");
+ }
+ connection.connect();
+ is = new BufferedInputStream(connection.getInputStream());
+ boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges"));
+ long transmitted = 0;
+ long expected = file.getExpectedSize();
+ if (tryResume && serverResumed) {
+ Logging.d(Config.LOGTAG, "server resumed");
+ transmitted = file.getSize();
+ updateProgress((int) ((((double) transmitted) / expected) * 100));
+ os = AbstractConnectionManager.createAppendedOutputStream(file);
+ } else {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ os = AbstractConnectionManager.createOutputStream(file, true);
+ }
+ int count = -1;
+ byte[] buffer = new byte[1024];
+ while ((count = is.read(buffer)) != -1) {
+ transmitted += count;
+ os.write(buffer, 0, count);
+ updateProgress((int) ((((double) transmitted) / expected) * 100));
+ if (canceled) {
+ throw new CancellationException();
+ }
+ }
+ } catch (CancellationException | IOException e) {
+ throw e;
+ } finally {
+ if (os != null) {
+ try {
+ os.flush();
+ } catch (final IOException ignored) {
+
+ }
+ }
+ StreamUtil.close(os);
+ StreamUtil.close(is);
+ wakeLock.release();
+ }
}
private void updateImageBounds() {
message.setType(Message.TYPE_FILE);
- mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
+ MessageUtil.updateFileParams(message, mUrl);
mXmppConnectionService.updateMessage(message);
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
index e337509b..1377e6ae 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.http;
import android.app.PendingIntent;
import android.os.PowerManager;
-import android.util.Log;
import android.util.Pair;
import java.io.FileNotFoundException;
@@ -15,6 +14,10 @@ import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
@@ -44,7 +47,6 @@ public class HttpUploadConnection implements Transferable {
private String mime;
private URL mGetUrl;
private URL mPutUrl;
- private boolean mUseTor = false;
private byte[] key = null;
@@ -55,7 +57,6 @@ public class HttpUploadConnection implements Transferable {
public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
this.mHttpConnectionManager = httpConnectionManager;
this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
- this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@Override
@@ -90,13 +91,13 @@ public class HttpUploadConnection implements Transferable {
mHttpConnectionManager.finishUploadConnection(this);
message.setTransferable(null);
mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
- FileBackend.close(mFileInputStream);
+ StreamUtil.close(mFileInputStream);
}
public void init(Message message, boolean delay) {
this.message = message;
this.account = message.getConversation().getAccount();
- this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
this.mime = this.file.getMimeType();
this.delayed = delay;
if (Config.ENCRYPT_ON_HTTP_UPLOADED
@@ -157,19 +158,16 @@ public class HttpUploadConnection implements Transferable {
PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid());
try {
wakeLock.acquire();
- Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
- if (mUseTor) {
- connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy());
- } else {
- connection = (HttpURLConnection) mPutUrl.openConnection();
- }
- if (connection instanceof HttpsURLConnection) {
+ Logging.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
+ connection = (HttpURLConnection) mPutUrl.openConnection();
+
+ if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
}
connection.setRequestMethod("PUT");
connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime);
- connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
+ connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
connection.setDoOutput(true);
connection.connect();
os = connection.getOutputStream();
@@ -186,12 +184,12 @@ public class HttpUploadConnection implements Transferable {
mFileInputStream.close();
int code = connection.getResponseCode();
if (code == 200 || code == 201) {
- Log.d(Config.LOGTAG, "finished uploading file");
+ Logging.d(Config.LOGTAG, "finished uploading file");
if (key != null) {
mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
}
- mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
- mXmppConnectionService.getFileBackend().updateMediaScanner(file);
+ MessageUtil.updateFileParams(message, mGetUrl);
+ FileBackend.updateMediaScanner(file, mXmppConnectionService);
message.setTransferable(null);
message.setCounterpart(message.getConversation().getJid().toBareJid());
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
@@ -218,12 +216,10 @@ public class HttpUploadConnection implements Transferable {
fail();
}
} catch (IOException e) {
- e.printStackTrace();
- Log.d(Config.LOGTAG,"http upload failed "+e.getMessage());
+ Logging.d(Config.LOGTAG, e.getMessage());
fail();
} finally {
- FileBackend.close(mFileInputStream);
- FileBackend.close(os);
+ StreamUtil.close(os);
if (connection != null) {
connection.disconnect();
}
diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
index 7d399073..ad368f11 100644
--- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.parser;
-
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -21,6 +20,15 @@ public abstract class AbstractParser {
this.mXmppConnectionService = service;
}
+ /**
+ * Gets the timestamp from the 'delay' element.
+ * Refer to XEP-0203: Delayed Delivery for details. @link{http://xmpp.org/extensions/xep-0203.html}
+ * @param element the element to find the child element 'delay' in.
+ * @return the time in milli seconds of the attribute 'stamp' of the
+ * element 'delay'. In case there is no 'delay' element or no 'stamp'
+ * attribute or the current time is less than the value of the 'stamp'
+ * attribute the current time is returned.
+ */
public static Long getTimestamp(Element element, Long defaultValue) {
Element delay = element.findChild("delay","urn:xmpp:delay");
if (delay != null) {
@@ -40,12 +48,27 @@ public abstract class AbstractParser {
return getTimestamp(packet,System.currentTimeMillis());
}
+ /**
+ * Parses the timestamp according to XEP-0082: XMPP Date and Time Profiles.
+ * @link{http://xmpp.org/extensions/xep-0082.html}
+ *
+ * @param timestamp the timestamp to parse
+ * @return Date
+ * @throws ParseException
+ */
public static Date parseTimestamp(String timestamp) throws ParseException {
- timestamp = timestamp.replace("Z", "+0000");
- SimpleDateFormat dateFormat;
- timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length());
- dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
- return dateFormat.parse(timestamp);
+ /*try {
+ Logging.d("TIMESTAMP", timestamp);
+ return DatatypeFactory.newInstance().newXMLGregorianCalendar(timestamp).toGregorianCalendar().getTime();
+ } catch (DatatypeConfigurationException e) {
+ Logging.d("TIMESTAMP", e.getMessage());
+ return new Date();
+ }*/
+ timestamp = timestamp.replace("Z", "+0000");
+ SimpleDateFormat dateFormat;
+ timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length());
+ dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
+ return dateFormat.parse(timestamp);
}
protected void updateLastseen(final AbstractStanza packet, final Account account, final boolean presenceOverwrite) {
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 5903d511..c03ed42f 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -22,10 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import de.thedevstack.android.logcat.Logging;
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.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
@@ -69,7 +71,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
contact.parseSubscriptionFromElement(item);
}
}
- mXmppConnectionService.getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
}
}
mXmppConnectionService.updateConversationUi();
@@ -155,7 +157,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
try {
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
} catch (Throwable e) {
- Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
+ Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
return null;
}
}
@@ -169,7 +171,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
} catch (Throwable e) {
- Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
+ Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
}
return identityKey;
}
@@ -209,7 +211,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) {
Element item = getItem(packet);
- Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null;
+ Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null;
Element signature = verification != null ? verification.findChild("signature") : null;
if (chain != null && signature != null) {
@@ -280,7 +282,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) &&
packet.fromServer(account)) {
// Block list or block push.
- Log.d(Config.LOGTAG, "Received blocklist update from server");
+ Logging.d(Config.LOGTAG, "Received blocklist update from server");
final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING);
final Element block = packet.findChild("block", Xmlns.BLOCKING);
final Collection<Element> items = blocklist != null ? blocklist.getChildren() :
@@ -308,7 +310,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
} else if (packet.hasChild("unblock", Xmlns.BLOCKING) &&
packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
- Log.d(Config.LOGTAG, "Received unblock update from server");
+ Logging.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren();
if (items.size() == 0) {
// No children to unblock == unblock all
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 9470dab8..073cc849 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -3,16 +3,22 @@ package eu.siacs.conversations.parser;
import android.util.Log;
import android.util.Pair;
+import de.tzur.conversations.Settings;
+
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
import java.net.URL;
import java.util.ArrayList;
import java.util.Set;
-import java.util.UUID;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.AvatarUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
@@ -21,6 +27,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.http.HttpConnectionManager;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -112,7 +119,7 @@ public class MessageParser extends AbstractParser implements
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());
+ Logging.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
}
return finishedMessage;
@@ -176,23 +183,23 @@ public class MessageParser extends AbstractParser implements
Avatar avatar = Avatar.parseMetadata(items);
if (avatar != null) {
avatar.owner = from.toBareJid();
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (AvatarUtil.isAvatarCached(avatar)) {
if (account.getJid().toBareJid().equals(from)) {
if (account.setAvatar(avatar.getFilename())) {
mXmppConnectionService.databaseBackend.updateAccount(account);
}
- mXmppConnectionService.getAvatarService().clear(account);
+ AvatarService.getInstance().clear(account);
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateAccountUi();
} else {
Contact contact = account.getRoster().getContact(from);
contact.setAvatar(avatar);
- mXmppConnectionService.getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateRosterUi();
}
} else {
- mXmppConnectionService.fetchAvatar(account, avatar);
+ AvatarService.getInstance().fetchAvatar(account, avatar);
}
}
} else if ("http://jabber.org/protocol/nick".equals(node)) {
@@ -201,12 +208,12 @@ public class MessageParser extends AbstractParser implements
if (nick != null && nick.getContent() != null) {
Contact contact = account.getRoster().getContact(from);
contact.setPresenceName(nick.getContent());
- mXmppConnectionService.getAvatarService().clear(account);
+ AvatarService.getInstance().clear(account);
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...");
+ } else if (ConversationsPlusPreferences.omemoEnabled() && AxolotlService.PEP_DEVICE_LIST.equals(node)) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.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();
@@ -267,7 +274,7 @@ public class MessageParser extends AbstractParser implements
serverMsgId = result.getAttribute("id");
query.incrementMessageCount();
} else if (query != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
return;
} else if (original.fromServer(account)) {
Pair<MessagePacket, Long> f;
@@ -291,10 +298,8 @@ public class MessageParser extends AbstractParser implements
final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
- final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
final Element oob = packet.findChild("x", "jabber:x:oob");
final boolean isOob = oob!= null && body != null && body.equals(oob.findChildContent("url"));
- final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
int status;
final Jid counterpart;
@@ -354,7 +359,7 @@ public class MessageParser extends AbstractParser implements
return;
}
} else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed));
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed));
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
}
} else if (pgpEncrypted != null && Config.supportOpenPgp()) {
@@ -402,42 +407,6 @@ public class MessageParser extends AbstractParser implements
updateLastseen(timestamp, account, packet.getFrom(), true);
}
- if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
- Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
- counterpart,
- message.getStatus() == Message.STATUS_RECEIVED,
- message.isCarbon());
- if (replacedMessage != null) {
- final boolean fingerprintsMatch = replacedMessage.getAxolotlFingerprint() == null
- || replacedMessage.getAxolotlFingerprint().equals(message.getAxolotlFingerprint());
- final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
- && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
- if (fingerprintsMatch && (trueCountersMatch || conversation.getMode() == Conversation.MODE_SINGLE)) {
- Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
- final String uuid = replacedMessage.getUuid();
- replacedMessage.setUuid(UUID.randomUUID().toString());
- replacedMessage.setBody(message.getBody());
- replacedMessage.setEdited(replacedMessage.getRemoteMsgId());
- replacedMessage.setRemoteMsgId(remoteMsgId);
- replacedMessage.setEncryption(message.getEncryption());
- if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
- replacedMessage.markUnread();
- }
- mXmppConnectionService.updateMessage(replacedMessage, uuid);
- mXmppConnectionService.getNotificationService().updateNotification(false);
- if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
- sendMessageReceipts(account, packet);
- }
- if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
- conversation.getAccount().getPgpDecryptionService().add(replacedMessage);
- }
- return;
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received message correction but verification didn't check out");
- }
- }
- }
-
boolean checkForDuplicates = query != null
|| (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay"))
|| message.getType() == Message.TYPE_PRIVATE;
@@ -463,7 +432,10 @@ public class MessageParser extends AbstractParser implements
account.activateGracePeriod();
}
} else {
- message.markUnread();
+ // only not mam messages should be marked as unread
+ if (query == null) {
+ message.markUnread();
+ }
}
}
@@ -471,7 +443,7 @@ public class MessageParser extends AbstractParser implements
mXmppConnectionService.updateConversationUi();
}
- if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
+ if (Settings.CONFIRM_MESSAGE_READ && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
sendMessageReceipts(account, packet);
}
@@ -482,17 +454,23 @@ public class MessageParser extends AbstractParser implements
conversation.endOtrIfNeeded();
}
- if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) {
+ if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) {
mXmppConnectionService.databaseBackend.createMessage(message);
}
- final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) {
- manager.createNewDownloadConnection(message);
- } else if (!message.isRead()) {
+ if (message.trusted()
+ && message.treatAsDownloadable() != Message.Decision.NEVER
+ && ConversationsPlusPreferences.autoAcceptFileSize() > 0
+ && ConversationsPlusPreferences.autoDownloadFileLink()) {
+ this.mXmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message);
+ } else {
if (query == null) {
mXmppConnectionService.getNotificationService().push(message);
} else if (query.getWith() == null) { // mam catchup
- mXmppConnectionService.getNotificationService().pushFromBacklog(message);
+ /*
+ Like suggested in https://bugs.thedevstack.de/task/156 user should be notified
+ in some other way of loaded messages.
+ */
+ // mXmppConnectionService.getNotificationService().pushFromBacklog(message);
}
}
} else if (!packet.hasChild("body")){ //no body
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index c2782d23..76da5a31 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -5,6 +5,9 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.AvatarUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -14,8 +17,10 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.generator.PresenceGenerator;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -42,12 +47,13 @@ public class PresenceParser extends AbstractParser implements
processConferencePresence(packet, mucOptions);
final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
if (!tileUserAfter.equals(tileUserBefore)) {
- mXmppConnectionService.getAvatarService().clear(mucOptions);
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": update tiles for " + conversation.getName());
+ AvatarService.getInstance().clear(conversation);
}
if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) {
- mXmppConnectionService.updateConversationUi();
+ UiUpdateHelper.updateConversationUi();
} else if (mucOptions.online()) {
- mXmppConnectionService.updateMucRosterUi();
+ UiUpdateHelper.updateMucRosterUi();
}
}
}
@@ -97,12 +103,12 @@ public class PresenceParser extends AbstractParser implements
}
if (avatar != null) {
avatar.owner = from;
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (AvatarUtil.isAvatarCached(avatar)) {
if (user.setAvatar(avatar)) {
- mXmppConnectionService.getAvatarService().clear(user);
+ AvatarService.getInstance().clear(user);
}
} else {
- mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
+ AvatarService.getInstance().fetchAvatar(mucOptions.getAccount(), avatar);
}
}
}
@@ -129,7 +135,7 @@ public class PresenceParser extends AbstractParser implements
} else if (!from.isBareJid()){
MucOptions.User user = mucOptions.deleteUser(from.getResourcepart());
if (user != null) {
- mXmppConnectionService.getAvatarService().clear(user);
+ AvatarService.getInstance().clear(user);
}
}
} else if (type.equals("error")) {
@@ -182,14 +188,14 @@ public class PresenceParser extends AbstractParser implements
Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
if (avatar != null && !contact.isSelf()) {
avatar.owner = from.toBareJid();
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (AvatarUtil.isAvatarCached(avatar)) {
if (contact.setAvatar(avatar)) {
- mXmppConnectionService.getAvatarService().clear(contact);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateRosterUi();
+ AvatarService.getInstance().clear(contact);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateRosterUi();
}
} else {
- mXmppConnectionService.fetchAvatar(account, avatar);
+ AvatarService.getInstance().fetchAvatar(account, avatar);
}
}
int sizeBefore = contact.getPresences().size();
@@ -240,7 +246,7 @@ public class PresenceParser extends AbstractParser implements
}
}
}
- mXmppConnectionService.updateRosterUi();
+ UiUpdateHelper.updateRosterUi();
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 2ce73796..e400095d 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -33,8 +33,9 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.json.JSONException;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
@@ -245,7 +246,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
).toString();
} catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
+ Logging.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
+ cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
+ ": " + ignored + ". Skipping...");
continue;
@@ -270,7 +271,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
cursor.getString(cursor.getColumnIndex(Contact.JID))
).toString();
} catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
+ Logging.e(Config.LOGTAG, "Failed to migrate Contact JID "
+ cursor.getString(cursor.getColumnIndex(Contact.JID))
+ ": " + ignored + ". Skipping...");
continue;
@@ -299,7 +300,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
"mobile"
).getDomainpart();
} catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
+ Logging.e(Config.LOGTAG, "Failed to migrate Account SERVER "
+ cursor.getString(cursor.getColumnIndex(Account.SERVER))
+ ": " + ignored + ". Skipping...");
continue;
@@ -365,7 +366,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
- if (oldVersion < 22 && newVersion >= 22) {
+ if (oldVersion < 22 && oldVersion >= 15 && newVersion >= 22) {
db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
}
@@ -946,7 +947,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
try {
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(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);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
}
cursor.close();
@@ -971,7 +972,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
try {
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(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);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
}
cursor.close();
@@ -1101,7 +1102,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
public void recreateAxolotlDb(SQLiteDatabase db) {
- Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
db.execSQL(CREATE_SESSIONS_STATEMENT);
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
@@ -1114,7 +1115,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void wipeAxolotlDb(Account account) {
String accountName = account.getUuid();
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
SQLiteDatabase db = this.getWritableDatabase();
String[] deleteArgs = {
accountName
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 18c195f1..6e766dd5 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -2,54 +2,36 @@ package eu.siacs.conversations.persistance;
import android.content.Intent;
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.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;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.Socket;
-import java.net.URL;
-import java.security.DigestOutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.exceptions.FileCopyException;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.ExifHelper;
-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 imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
- private XmppConnectionService mXmppConnectionService;
-
- public FileBackend(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- private void createNoMedia() {
+ public static void createNoMedia() {
final File nomedia = new File(getConversationsFileDirectory()+".nomedia");
if (!nomedia.exists()) {
try {
@@ -60,31 +42,29 @@ public class FileBackend {
}
}
- public void updateMediaScanner(File file) {
+ public static void updateMediaScanner(File file, XmppConnectionService xmppConnectionService) {
if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())) {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
- mXmppConnectionService.sendBroadcast(intent);
- } else {
- createNoMedia();
+ xmppConnectionService.sendBroadcast(intent);
}
}
- public boolean deleteFile(Message message) {
+ public static boolean deleteFile(Message message, XmppConnectionService xmppConnectionService) {
File file = getFile(message);
if (file.delete()) {
- updateMediaScanner(file);
+ updateMediaScanner(file, xmppConnectionService);
return true;
} else {
return false;
}
}
- public DownloadableFile getFile(Message message) {
+ public static DownloadableFile getFile(Message message) {
return getFile(message, true);
}
- public DownloadableFile getFile(Message message, boolean decrypted) {
+ public static DownloadableFile getFile(Message message, boolean decrypted) {
final boolean encrypted = !decrypted
&& (message.getEncryption() == Message.ENCRYPTION_PGP
|| message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
@@ -111,88 +91,29 @@ public class FileBackend {
}
public static String getConversationsFileDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ConversationsPlusPreferences.fileTransferFolder() + File.separator;
}
public static String getConversationsImageDirectory() {
- return Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES).getAbsolutePath()
- + "/Conversations/";
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + ConversationsPlusPreferences.imgTransferFolder() + File.separator;
}
- public Bitmap resize(Bitmap originalBitmap, int size) {
- int w = originalBitmap.getWidth();
- int h = originalBitmap.getHeight();
- if (Math.max(w, h) > size) {
- int scalledW;
- int scalledH;
- if (w <= h) {
- scalledW = (int) (w / ((double) h / size));
- scalledH = size;
- } else {
- scalledW = size;
- scalledH = (int) (h / ((double) w / size));
- }
- Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
- if (originalBitmap != null && !originalBitmap.isRecycled()) {
- originalBitmap.recycle();
- }
- return result;
- } else {
- return originalBitmap;
- }
- }
-
- public static Bitmap rotate(Bitmap bitmap, int degree) {
- if (degree == 0) {
- return bitmap;
- }
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
- Matrix mtx = new Matrix();
- mtx.postRotate(degree);
- Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
- if (bitmap != null && !bitmap.isRecycled()) {
- bitmap.recycle();
- }
- return result;
- }
-
- public boolean useImageAsIs(Uri uri) {
- String path = getOriginalPath(uri);
- if (path == null) {
- return false;
- }
- File file = new File(path);
- long size = file.length();
- if (size == 0 || size >= Config.IMAGE_MAX_SIZE ) {
- return false;
- }
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- try {
- BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options);
- if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) {
- return false;
- }
- return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
- } catch (FileNotFoundException e) {
- return false;
- }
- }
+ private static String getPrivateFileDirectoryPath() {
+ return ConversationsPlusApplication.getPrivateFilesDir().getAbsolutePath();
+ }
- public String getOriginalPath(Uri uri) {
- return FileUtils.getPath(mXmppConnectionService,uri);
- }
+ private static String getPrivateImageDirectoryPath() {
+ return FileBackend.getPrivateFileDirectoryPath() + File.separator + "Images" + File.separator;
+ }
- public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
+ public static void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
file.getParentFile().mkdirs();
OutputStream os = null;
InputStream is = null;
try {
file.createNewFile();
os = new FileOutputStream(file);
- is = mXmppConnectionService.getContentResolver().openInputStream(uri);
+ is = StreamUtil.openInputStreamFromContentResolver(uri);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
@@ -205,420 +126,69 @@ public class FileBackend {
e.printStackTrace();
throw new FileCopyException(R.string.error_io_exception);
} finally {
- close(os);
- close(is);
+ StreamUtil.close(os);
+ StreamUtil.close(is);
}
- Log.d(Config.LOGTAG, "output file name " + file.getAbsolutePath());
+ Logging.d(Config.LOGTAG, "output file name " + file);
}
- public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ public static void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
- String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri);
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
message.setRelativeFilePath(message.getUuid() + "." + extension);
- copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
- }
-
- private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException {
- file.getParentFile().mkdirs();
- InputStream is = null;
- OutputStream os = null;
- try {
- file.createNewFile();
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- Bitmap originalBitmap;
- BitmapFactory.Options options = new BitmapFactory.Options();
- int inSampleSize = (int) Math.pow(2, sampleSize);
- Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
- options.inSampleSize = inSampleSize;
- originalBitmap = BitmapFactory.decodeStream(is, null, options);
- is.close();
- if (originalBitmap == null) {
- throw new FileCopyException(R.string.error_not_an_image_file);
- }
- Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE);
- int rotation = getRotation(image);
- scaledBitmap = rotate(scaledBitmap, rotation);
- boolean targetSizeReached = false;
- int quality = Config.IMAGE_QUALITY;
- while(!targetSizeReached) {
- os = new FileOutputStream(file);
- boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os);
- if (!success) {
- throw new FileCopyException(R.string.error_compressing_image);
- }
- os.flush();
- targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50;
- quality -= 5;
- }
- scaledBitmap.recycle();
- return;
- } catch (FileNotFoundException e) {
- throw new FileCopyException(R.string.error_file_not_found);
- } catch (IOException e) {
- e.printStackTrace();
- throw new FileCopyException(R.string.error_io_exception);
- } catch (SecurityException e) {
- throw new FileCopyException(R.string.error_security_exception_during_image_copy);
- } catch (OutOfMemoryError e) {
- ++sampleSize;
- if (sampleSize <= 3) {
- copyImageToPrivateStorage(file, image, sampleSize);
- } else {
- throw new FileCopyException(R.string.error_out_of_memory);
- }
- } catch (NullPointerException e) {
- throw new FileCopyException(R.string.error_io_exception);
- } finally {
- close(os);
- close(is);
- }
- }
-
- public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException {
- copyImageToPrivateStorage(file, image, 0);
- }
-
- public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException {
- switch(Config.IMAGE_FORMAT) {
- case JPEG:
- message.setRelativeFilePath(message.getUuid()+".jpg");
- break;
- case PNG:
- message.setRelativeFilePath(message.getUuid()+".png");
- break;
- case WEBP:
- message.setRelativeFilePath(message.getUuid()+".webp");
- break;
- }
- copyImageToPrivateStorage(getFile(message), image);
- updateFileParams(message);
- }
-
- private int getRotation(File file) {
- return getRotation(Uri.parse("file://"+file.getAbsolutePath()));
- }
-
- private int getRotation(Uri image) {
- InputStream is = null;
- try {
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- return ExifHelper.getOrientation(is);
- } catch (FileNotFoundException e) {
- return 0;
- } finally {
- close(is);
- }
- }
-
- public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
- throws FileNotFoundException {
- Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(message.getUuid());
- if ((thumbnail == null) && (!cacheOnly)) {
- File file = getFile(message);
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(file, size);
- Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
- if (fullsize == null) {
- throw new FileNotFoundException();
- }
- thumbnail = resize(fullsize, size);
- thumbnail = rotate(thumbnail, getRotation(file));
- this.mXmppConnectionService.getBitmapCache().put(message.getUuid(),thumbnail);
- }
- return thumbnail;
- }
-
- public Uri getTakePhotoUri() {
+ copyFileToPrivateStorage(getFile(message), uri);
+ }
+
+ public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException {
+ message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".jpg");
+ DownloadableFile file = getFile(message);
+ file.getParentFile().mkdirs();
+ OutputStream os = null;
+ try {
+ file.createNewFile();
+ os = new FileOutputStream(file);
+
+ boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 75, os);
+ if (!success) {
+ throw new FileCopyException(R.string.error_compressing_image);
+ }
+ os.flush();
+ } catch (IOException e) {
+ throw new FileCopyException(R.string.error_io_exception, e);
+ } catch (SecurityException e) {
+ throw new FileCopyException(R.string.error_security_exception_during_image_copy);
+ } catch (NullPointerException e) {
+ throw new FileCopyException(R.string.error_io_exception);
+ } finally {
+ StreamUtil.close(os);
+ }
+ return file;
+ }
+
+ public static 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");
+ pathBuilder.append("IMG_" + imageDateFormat.format(new Date()) + ".jpg");
Uri uri = Uri.parse("file://" + pathBuilder.toString());
File file = new File(uri.toString());
file.getParentFile().mkdirs();
return uri;
}
- public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
- try {
- Avatar avatar = new Avatar();
- Bitmap bm = cropCenterSquare(image, size);
- if (bm == null) {
- return null;
- }
- ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
- Base64OutputStream mBase64OutputSttream = new Base64OutputStream(
- mByteArrayOutputStream, Base64.DEFAULT);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(
- mBase64OutputSttream, digest);
- if (!bm.compress(format, 75, mDigestOutputStream)) {
- return null;
- }
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
- avatar.image = new String(mByteArrayOutputStream.toByteArray());
- return avatar;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } catch (IOException e) {
- return null;
- }
- }
-
- public boolean isAvatarCached(Avatar avatar) {
- File file = new File(getAvatarPath(avatar.getFilename()));
- return file.exists();
- }
-
- public boolean save(Avatar avatar) {
- File file;
- if (isAvatarCached(avatar)) {
- file = new File(getAvatarPath(avatar.getFilename()));
- } else {
- String filename = getAvatarPath(avatar.getFilename());
- file = new File(filename + ".tmp");
- file.getParentFile().mkdirs();
- OutputStream os = null;
- try {
- file.createNewFile();
- os = new FileOutputStream(file);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- digest.reset();
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
- mDigestOutputStream.write(avatar.getImageAsBytes());
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- String sha1sum = CryptoHelper.bytesToHex(digest.digest());
- if (sha1sum.equals(avatar.sha1sum)) {
- file.renameTo(new File(filename));
- } else {
- Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
- file.delete();
- return false;
- }
- } catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) {
- return false;
- } finally {
- close(os);
- }
- }
- avatar.size = file.length();
- return true;
- }
-
- public String getAvatarPath(String avatar) {
- return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
- }
-
- public Uri getAvatarUri(String avatar) {
- return Uri.parse("file:" + getAvatarPath(avatar));
- }
-
- public Bitmap cropCenterSquare(Uri image, int size) {
- if (image == null) {
- return null;
- }
- InputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(image, size);
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- return null;
- }
- Bitmap input = BitmapFactory.decodeStream(is, null, options);
- if (input == null) {
- return null;
- } else {
- input = rotate(input, getRotation(image));
- return cropCenterSquare(input, size);
- }
- } catch (SecurityException e) {
- return null; // happens for example on Android 6.0 if contacts permissions get revoked
- } catch (FileNotFoundException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
- if (image == null) {
- return null;
- }
- InputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(image, Math.max(newHeight, newWidth));
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- return null;
- }
- Bitmap source = BitmapFactory.decodeStream(is, null, options);
- if (source == null) {
- return null;
- }
- int sourceWidth = source.getWidth();
- int sourceHeight = source.getHeight();
- float xScale = (float) newWidth / sourceWidth;
- float yScale = (float) newHeight / sourceHeight;
- float scale = Math.max(xScale, yScale);
- float scaledWidth = scale * sourceWidth;
- float scaledHeight = scale * sourceHeight;
- float left = (newWidth - scaledWidth) / 2;
- float top = (newHeight - scaledHeight) / 2;
-
- RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
- Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(dest);
- canvas.drawBitmap(source, null, targetRect, null);
- if (source != null && !source.isRecycled()) {
- source.recycle();
- }
- return dest;
- } catch (SecurityException e) {
- return null; //android 6.0 with revoked permissions for example
- } catch (FileNotFoundException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public Bitmap cropCenterSquare(Bitmap input, int size) {
- int w = input.getWidth();
- int h = input.getHeight();
-
- float scale = Math.max((float) size / h, (float) size / w);
-
- float outWidth = scale * w;
- float outHeight = scale * h;
- float left = (size - outWidth) / 2;
- float top = (size - outHeight) / 2;
- RectF target = new RectF(left, top, left + outWidth, top + outHeight);
-
- Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- canvas.drawBitmap(input, null, target, null);
- if (input != null && !input.isRecycled()) {
- input.recycle();
- }
- return output;
- }
-
- private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options);
- return calcSampleSize(options, size);
- }
-
- private static int calcSampleSize(File image, int size) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(image.getAbsolutePath(), options);
- return calcSampleSize(options, size);
- }
-
- public static int calcSampleSize(BitmapFactory.Options options, int size) {
- int height = options.outHeight;
- int width = options.outWidth;
- int inSampleSize = 1;
-
- if (height > size || width > size) {
- int halfHeight = height / 2;
- int halfWidth = width / 2;
-
- while ((halfHeight / inSampleSize) > size
- && (halfWidth / inSampleSize) > size) {
- inSampleSize *= 2;
- }
- }
- return inSampleSize;
- }
-
- public Uri getJingleFileUri(Message message) {
+ public static Uri getJingleFileUri(Message message) {
File file = getFile(message);
return Uri.parse("file://" + file.getAbsolutePath());
}
- public void updateFileParams(Message message) {
- updateFileParams(message,null);
- }
-
- public void updateFileParams(Message message, URL url) {
- DownloadableFile file = getFile(message);
- if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- int rotation = getRotation(file);
- boolean rotated = rotation == 90 || rotation == 270;
- int imageHeight = rotated ? options.outWidth : options.outHeight;
- int imageWidth = rotated ? options.outHeight : options.outWidth;
- if (url == null) {
- message.setBody(Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
- } else {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
- }
- } else {
- if (url != null) {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
- } else {
- message.setBody(Long.toString(file.getSize()));
- }
- }
-
- }
-
- public class FileCopyException extends Exception {
- private static final long serialVersionUID = -1010013599132881427L;
- private int resId;
-
- public FileCopyException(int resId) {
- this.resId = resId;
- }
-
- public int getResId() {
- return resId;
- }
- }
-
- public Bitmap getAvatar(String avatar, int size) {
- if (avatar == null) {
- return null;
- }
- Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
- if (bm == null) {
- return null;
- }
- return bm;
- }
-
- public boolean isFileAvailable(Message message) {
+ public static boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
- public static void close(Closeable stream) {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- }
- }
- }
-
- public static void close(Socket socket) {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException e) {
- }
- }
- }
+ private FileBackend() {
+ // Static helper class
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
index 8d02f975..7728c38a 100644
--- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
@@ -44,16 +44,6 @@ public class AbstractConnectionManager {
return this.mXmppConnectionService;
}
- public long getAutoAcceptFileSize() {
- String config = this.mXmppConnectionService.getPreferences().getString(
- "auto_accept_file_size", "524288");
- try {
- return Long.parseLong(config);
- } catch (NumberFormatException e) {
- return 524288;
- }
- }
-
public boolean hasStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java
index 276be10d..f4cbdf3d 100644
--- a/src/main/java/eu/siacs/conversations/services/AvatarService.java
+++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java
@@ -8,9 +8,20 @@ import android.graphics.Typeface;
import android.net.Uri;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.utils.AvatarUtil;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketGenerator;
+import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketParser;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
@@ -18,7 +29,14 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
+import eu.siacs.conversations.generator.IqGenerator;
+import eu.siacs.conversations.persistance.DatabaseBackend;
+import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.pep.Avatar;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class AvatarService {
@@ -30,31 +48,31 @@ public class AvatarService {
private static final String PREFIX_CONVERSATION = "conversation";
private static final String PREFIX_ACCOUNT = "account";
private static final String PREFIX_GENERIC = "generic";
+ private static final AvatarService INSTANCE = new AvatarService();
- final private ArrayList<Integer> sizes = new ArrayList<>();
-
- protected XmppConnectionService mXmppConnectionService = null;
-
- public AvatarService(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
+ final private ArrayList<Integer> sizes = new ArrayList<>();
+ private final List<String> mInProgressAvatarFetches = new ArrayList<>();
+
+ public static AvatarService getInstance() {
+ return INSTANCE;
+ }
private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
final String KEY = key(contact, size);
- Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap avatar = ImageUtil.getBitmapFromCache(KEY);
if (avatar != null || cachedOnly) {
return avatar;
}
if (contact.getProfilePhoto() != null) {
- avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
+ avatar = ImageUtil.cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
}
if (avatar == null && contact.getAvatar() != null) {
- avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size);
+ avatar = AvatarUtil.getAvatar(contact.getAvatar(), size);
}
if (avatar == null) {
avatar = get(contact.getDisplayName(), size, cachedOnly);
}
- this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+ ImageUtil.addBitmapToCache(KEY, avatar);
return avatar;
}
@@ -69,12 +87,12 @@ public class AvatarService {
private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) {
final String KEY = key(user, size);
- Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap avatar = ImageUtil.getBitmapFromCache(KEY);
if (avatar != null || cachedOnly) {
return avatar;
}
if (user.getAvatar() != null) {
- avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size);
+ avatar = AvatarUtil.getAvatar(user.getAvatar(), size);
}
if (avatar == null) {
Contact contact = user.getContact();
@@ -84,15 +102,14 @@ public class AvatarService {
avatar = get(user.getName(), size, cachedOnly);
}
}
- this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+ ImageUtil.addBitmapToCache(KEY, avatar);
return avatar;
}
public void clear(Contact contact) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(
- key(contact, size));
+ ImageUtil.removeBitmapFromCache(key(contact, size));
}
}
}
@@ -158,7 +175,7 @@ public class AvatarService {
private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) {
final String KEY = key(mucOptions, size);
- Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap bitmap = ImageUtil.getBitmapFromCache(KEY);
if (bitmap != null || cachedOnly) {
return bitmap;
}
@@ -195,14 +212,14 @@ public class AvatarService {
drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1,
size, size);
}
- this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
+ ImageUtil.addBitmapToCache(KEY, bitmap);
return bitmap;
}
public void clear(MucOptions options) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(key(options, size));
+ ImageUtil.removeBitmapFromCache(key(options, size));
}
}
}
@@ -223,16 +240,15 @@ public class AvatarService {
public Bitmap get(Account account, int size, boolean cachedOnly) {
final String KEY = key(account, size);
- Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap avatar = ImageUtil.getBitmapFromCache(KEY);
if (avatar != null || cachedOnly) {
return avatar;
}
- avatar = mXmppConnectionService.getFileBackend().getAvatar(
- account.getAvatar(), size);
+ avatar = AvatarUtil.getAvatar(account.getAvatar(), size);
if (avatar == null) {
avatar = get(account.getJid().toBareJid().toString(), size,false);
}
- mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+ ImageUtil.addBitmapToCache(KEY, avatar);
return avatar;
}
@@ -257,7 +273,7 @@ public class AvatarService {
public void clear(Account account) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(key(account, size));
+ ImageUtil.removeBitmapFromCache(key(account, size));
}
}
}
@@ -265,7 +281,7 @@ public class AvatarService {
public void clear(MucOptions.User user) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(key(user, size));
+ ImageUtil.removeBitmapFromCache(key(user, size));
}
}
}
@@ -286,15 +302,14 @@ public class AvatarService {
public Bitmap get(final String name, final int size, boolean cachedOnly) {
final String KEY = key(name, size);
- Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap bitmap = ImageUtil.getBitmapFromCache(KEY);
if (bitmap != null || cachedOnly) {
return bitmap;
}
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
- final String trimmedName = name.trim();
- drawTile(canvas, trimmedName, 0, 0, size, size);
- mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
+ drawTile(canvas, name, 0, 0, size, size);
+ ImageUtil.addBitmapToCache(KEY, bitmap);
return bitmap;
}
@@ -335,14 +350,13 @@ public class AvatarService {
if (contact.getProfilePhoto() != null) {
uri = Uri.parse(contact.getProfilePhoto());
} else if (contact.getAvatar() != null) {
- uri = mXmppConnectionService.getFileBackend().getAvatarUri(
- contact.getAvatar());
+ uri = AvatarUtil.getAvatarUri(contact.getAvatar());
}
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
}
} else if (user.getAvatar() != null) {
- Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar());
+ Uri uri = AvatarUtil.getAvatarUri(user.getAvatar());
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
}
@@ -355,7 +369,7 @@ public class AvatarService {
private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) {
String avatar = account.getAvatar();
if (avatar != null) {
- Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar);
+ Uri uri = AvatarUtil.getAvatarUri(avatar);
if (uri != null) {
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
@@ -367,7 +381,8 @@ public class AvatarService {
private boolean drawTile(Canvas canvas, String name, int left, int top, int right, int bottom) {
if (name != null) {
- final String letter = name.isEmpty() ? "X" : name.substring(0, 1);
+ String trimmedName = name.trim();
+ final String letter = trimmedName.isEmpty() ? "X" : trimmedName.substring(0, 1);
final int color = UIHelper.getColorForName(name);
drawTile(canvas, letter, color, left, top, right, bottom);
return true;
@@ -377,8 +392,7 @@ public class AvatarService {
private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) {
if (uri != null) {
- Bitmap bitmap = mXmppConnectionService.getFileBackend()
- .cropCenter(uri, bottom - top, right - left);
+ Bitmap bitmap = ImageUtil.cropCenter(uri, bottom - top, right - left);
if (bitmap != null) {
drawTile(canvas, bitmap, left, top, right, bottom);
return true;
@@ -393,4 +407,221 @@ public class AvatarService {
canvas.drawBitmap(bm, null, dst, null);
return true;
}
+
+ public void publishAvatar(final Account account,
+ final Uri image,
+ final UiCallback<Avatar> callback) {
+ final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
+ final int size = Config.AVATAR_SIZE;
+ final Avatar avatar = AvatarUtil.getPepAvatar(image, size, format);
+ if (avatar != null) {
+ avatar.height = size;
+ avatar.width = size;
+ if (format.equals(Bitmap.CompressFormat.WEBP)) {
+ avatar.type = "image/webp";
+ } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
+ avatar.type = "image/jpeg";
+ } else if (format.equals(Bitmap.CompressFormat.PNG)) {
+ avatar.type = "image/png";
+ }
+ if (!AvatarUtil.save(avatar)) {
+ callback.error(R.string.error_saving_avatar, avatar);
+ return;
+ }
+ final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarPacket(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket result) {
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarMetadataPacket(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account,
+ IqPacket result) {
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ if (account.setAvatar(avatar.getFilename())) {
+ AvatarService.getInstance().clear(account);
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account);
+ }
+ callback.success(avatar);
+ } else {
+ callback.error(
+ R.string.error_publish_avatar_server_reject,
+ avatar);
+ }
+ }
+ });
+ } else {
+ callback.error(
+ R.string.error_publish_avatar_server_reject,
+ avatar);
+ }
+ }
+ });
+ } else {
+ callback.error(R.string.error_publish_avatar_converting, null);
+ }
+ }
+
+ private static String generateFetchKey(Account account, final Avatar avatar) {
+ return account.getJid().toBareJid()+"_"+avatar.owner+"_"+avatar.sha1sum;
+ }
+
+ public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ final String KEY = generateFetchKey(account, avatar);
+ synchronized(this.mInProgressAvatarFetches) {
+ if (this.mInProgressAvatarFetches.contains(KEY)) {
+ return;
+ } else {
+ switch (avatar.origin) {
+ case PEP:
+ this.mInProgressAvatarFetches.add(KEY);
+ fetchAvatarPep(account, avatar, callback);
+ break;
+ case VCARD:
+ this.mInProgressAvatarFetches.add(KEY);
+ fetchAvatarVcard(account, avatar, callback);
+ break;
+ }
+ }
+ }
+ }
+
+ private void fetchAvatarPep(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ IqPacket packet = AvatarPacketGenerator.generateRetrieveAvatarPacket(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket result) {
+ synchronized (mInProgressAvatarFetches) {
+ mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
+ }
+ final String ERROR = account.getJid().toBareJid()
+ + ": fetching avatar for " + avatar.owner + " failed ";
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ avatar.image = AvatarPacketParser.parseAvatarData(result);
+ if (avatar.image != null) {
+ if (AvatarUtil.save(avatar)) {
+ if (account.getJid().toBareJid().equals(avatar.owner)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account);
+ }
+ AvatarService.this.clear(account);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateAccountUi();
+ } else {
+ Contact contact = account.getRoster()
+ .getContact(avatar.owner);
+ contact.setAvatar(avatar);
+ AvatarService.this.clear(contact);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateRosterUi();
+ }
+ if (callback != null) {
+ callback.success(avatar);
+ }
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": succesfuly fetched pep avatar for " + avatar.owner);
+ return;
+ }
+ } else {
+
+ Logging.d(Config.LOGTAG, ERROR + "(parsing error)");
+ }
+ } else {
+ Element error = result.findChild("error");
+ if (error == null) {
+ Logging.d(Config.LOGTAG, ERROR + "(server error)");
+ } else {
+ Logging.d(Config.LOGTAG, ERROR + error.toString());
+ }
+ }
+ if (callback != null) {
+ callback.error(0, null);
+ }
+
+ }
+ });
+ }
+
+ private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ IqPacket packet = IqGenerator.retrieveVcardAvatar(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ synchronized (mInProgressAvatarFetches) {
+ mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
+ }
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element vCard = packet.findChild("vCard", "vcard-temp");
+ Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
+ String image = photo != null ? photo.findChildContent("BINVAL") : null;
+ if (image != null) {
+ avatar.image = image;
+ if (AvatarUtil.save(avatar)) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": successfully fetched vCard avatar for " + avatar.owner);
+ Contact contact = account.getRoster()
+ .getContact(avatar.owner);
+ contact.setAvatar(avatar);
+ AvatarService.this.clear(contact);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateRosterUi();
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public void checkForAvatar(Account account, final UiCallback<Avatar> callback) {
+ IqPacket packet = AvatarPacketGenerator.generateRetrieveAvatarMetadataPacket(null);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element pubsub = packet.findChild("pubsub",
+ "http://jabber.org/protocol/pubsub");
+ if (pubsub != null) {
+ Element items = pubsub.findChild("items");
+ if (items != null) {
+ Avatar avatar = Avatar.parseMetadata(items);
+ if (avatar != null) {
+ avatar.owner = account.getJid().toBareJid();
+ if (AvatarUtil.isAvatarCached(avatar)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account);
+ }
+ AvatarService.this.clear(account);
+ callback.success(avatar);
+ } else {
+ fetchAvatarPep(account, avatar, callback);
+ }
+ return;
+ }
+ }
+ }
+ }
+ callback.error(0, null);
+ }
+ });
+ }
+
+ public void fetchAvatar(Account account, Avatar avatar) {
+ fetchAvatar(account, avatar, null);
+ }
+
+ public void clearFetchInProgress(Account account) {
+ synchronized (this.mInProgressAvatarFetches) {
+ for(Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext();) {
+ final String KEY = iterator.next();
+ if (KEY.startsWith(account.getJid().toBareJid()+"_")) {
+ iterator.remove();
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
index c2a45bf2..52894f07 100644
--- a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
+++ b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
@@ -1,7 +1,6 @@
package eu.siacs.conversations.services;
import android.annotation.TargetApi;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -11,11 +10,8 @@ import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.SystemClock;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
-import android.util.DisplayMetrics;
-import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -51,7 +47,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
for(int i = 0; i < Math.min(conversations.size(),MAX_TARGETS); ++i) {
final Conversation conversation = conversations.get(i);
final String name = conversation.getName();
- final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel));
+ final Icon icon = Icon.createWithBitmap(AvatarService.getInstance().get(conversation, pixel));
final float score = (1.0f / MAX_TARGETS) * i;
final Bundle extras = new Bundle();
extras.putString("uuid", conversation.getUuid());
diff --git a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
index 87e65931..53d0caaf 100644
--- a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
+++ b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
index e8616bd8..40dbf1f5 100644
--- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
+++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.services;
-import android.util.Log;
import android.util.Pair;
import java.math.BigInteger;
@@ -9,6 +8,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -30,7 +30,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
public enum PagingOrder {
NORMAL,
REVERSE
- }
+ };
public MessageArchiveService(final XmppConnectionService service) {
this.mXmppConnectionService = service;
@@ -96,18 +96,25 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
return this.query(conversation,conversation.getLastMessageTransmitted(),end);
}
- public Query query(Conversation conversation, long start, long end) {
- synchronized (this.queries) {
- if (start > end) {
- return null;
- }
- final Query query = new Query(conversation, start, end,PagingOrder.REVERSE);
- query.reference = conversation.getFirstMamReference();
- this.queries.add(query);
- this.execute(query);
- return query;
- }
- }
+ public Query query(Conversation conversation, long start, long end) {
+ return this.query(conversation, start, end, null);
+ }
+
+ public Query query(Conversation conversation, long start, long end, XmppConnectionService.OnMoreMessagesLoaded callback) {
+ synchronized (this.queries) {
+ if (start > end) {
+ return null;
+ }
+ final Query query = new Query(conversation, start, end,PagingOrder.REVERSE);
+ query.reference = conversation.getFirstMamReference();
+ this.queries.add(query);
+ if (null != callback) {
+ query.setCallback(callback);
+ }
+ this.execute(query);
+ return query;
+ }
+ }
public void executePendingQueries(final Account account) {
List<Query> pending = new ArrayList<>();
@@ -128,7 +135,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
private void execute(final Query query) {
final Account account= query.getAccount();
if (account.getStatus() == Account.State.ONLINE) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString());
IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
@@ -137,11 +144,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
synchronized (MessageArchiveService.this.queries) {
MessageArchiveService.this.queries.remove(query);
if (query.hasCallback()) {
- query.callback(false);
+ query.callback();
}
}
} else if (packet.getType() != IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString());
finalizeQuery(query, true);
}
}
@@ -169,8 +176,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
if (query.hasCallback()) {
- query.callback(done);
+ query.callback();
} else {
+ if (null != conversation) {
+ conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0);
+ }
this.mXmppConnectionService.updateConversationUi();
}
}
@@ -213,7 +223,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
if (complete || relevant == null || abort) {
final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0;
this.finalizeQuery(query, done);
- Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done));
+ Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done));
if (query.getWith() == null && query.getMessageCount() > 0) {
mXmppConnectionService.getNotificationService().finishBacklog(true);
}
@@ -332,10 +342,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
this.callback = callback;
}
- public void callback(boolean done) {
+ public void callback() {
if (this.callback != null) {
this.callback.onMoreMessagesLoaded(messageCount,conversation);
- if (done) {
+ if (messageCount <= 0) {
this.callback.informUser(R.string.no_more_history_on_server);
}
}
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index cec9a3ef..0f40e8d7 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -5,7 +5,6 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
@@ -26,9 +25,12 @@ import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -36,7 +38,6 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
-import eu.siacs.conversations.ui.TimePreference;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
@@ -60,10 +61,11 @@ public class NotificationService {
public boolean notify(final Message message) {
return (message.getStatus() == Message.STATUS_RECEIVED)
- && notificationsEnabled()
- && !message.getConversation().isMuted()
- && (message.getConversation().alwaysNotify() || wasHighlightedOrPrivate(message)
- );
+ && !message.isRead()
+ && ConversationsPlusPreferences.showNotification()
+ && !message.getConversation().isMuted()
+ && (message.getConversation().alwaysNotify() || wasHighlightedOrPrivate(message)
+ );
}
public void notifyPebble(final Message message) {
@@ -77,7 +79,7 @@ public class NotificationService {
final String notificationData = new JSONArray().put(jsonData).toString();
i.putExtra("messageType", "PEBBLE_ALERT");
- i.putExtra("sender", "Conversations"); /* XXX: Shouldn't be hardcoded, e.g., AbstractGenerator.APP_NAME); */
+ i.putExtra("sender", ConversationsPlusApplication.getName());
i.putExtra("notificationData", notificationData);
// notify Pebble App
i.setPackage("com.getpebble.android");
@@ -87,17 +89,12 @@ public class NotificationService {
mXmppConnectionService.sendBroadcast(i);
}
-
- public boolean notificationsEnabled() {
- return mXmppConnectionService.getPreferences().getBoolean("show_notification", true);
- }
-
public boolean isQuietHours() {
- if (!mXmppConnectionService.getPreferences().getBoolean("enable_quiet_hours", false)) {
+ if (!ConversationsPlusPreferences.enableQuietHours()) {
return false;
}
- final long startTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
- final long endTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
+ final long startTime = ConversationsPlusPreferences.quietHoursStart() % Config.MILLISECONDS_IN_DAY;
+ final long endTime = ConversationsPlusPreferences.quietHoursEnd() % Config.MILLISECONDS_IN_DAY;
final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY;
if (endTime < startTime) {
@@ -134,10 +131,10 @@ public class NotificationService {
}
public void push(final Message message) {
- mXmppConnectionService.updateUnreadCountBadge();
if (!notify(message)) {
return;
}
+ mXmppConnectionService.updateUnreadCountBadge();
final boolean isScreenOn = mXmppConnectionService.isInteractive();
if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
return;
@@ -174,12 +171,10 @@ public class NotificationService {
}
public void updateNotification(final boolean notify) {
- final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
- .getSystemService(Context.NOTIFICATION_SERVICE);
- final SharedPreferences preferences = mXmppConnectionService.getPreferences();
+ final NotificationManager notificationManager = (NotificationManager) ConversationsPlusApplication.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
- final String ringtone = preferences.getString("notification_ringtone", null);
- final boolean vibrate = preferences.getBoolean("vibrate_on_notification", true);
+ final String ringtone = ConversationsPlusPreferences.notificationRingtone();
+ final boolean vibrate = ConversationsPlusPreferences.vibrateOnNotification();
if (notifications.size() == 0) {
notificationManager.cancel(NOTIFICATION_ID);
@@ -210,7 +205,7 @@ public class NotificationService {
mBuilder.setDefaults(0);
mBuilder.setSmallIcon(R.drawable.ic_notification);
mBuilder.setDeleteIntent(createDeleteIntent());
- mBuilder.setLights(0xff00FF00, 2000, 3000);
+ mBuilder.setLights(Settings.LED_COLOR, 2000, 4000);
final Notification notification = mBuilder.build();
notificationManager.notify(NOTIFICATION_ID, notification);
}
@@ -262,8 +257,7 @@ public class NotificationService {
final ArrayList<Message> messages = notifications.values().iterator().next();
if (messages.size() >= 1) {
final Conversation conversation = messages.get(0).getConversation();
- mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
- .get(conversation, getPixel(64)));
+ mBuilder.setLargeIcon(AvatarService.getInstance().get(conversation, getPixel(64)));
mBuilder.setContentTitle(conversation.getName());
if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
int count = messages.size();
@@ -300,8 +294,7 @@ public class NotificationService {
private void modifyForImage(final Builder builder, final Message message,
final ArrayList<Message> messages, final boolean notify) {
try {
- final Bitmap bitmap = mXmppConnectionService.getFileBackend()
- .getThumbnail(message, getPixel(288), false);
+ final Bitmap bitmap = ImageUtil.getThumbnail(message, getPixel(288), false);
final ArrayList<Message> tmp = new ArrayList<>();
for (final Message msg : messages) {
if (msg.getType() == Message.TYPE_TEXT
@@ -361,7 +354,7 @@ public class NotificationService {
&& message.getEncryption() != Message.ENCRYPTION_PGP
&& message.getFileParams().height > 0) {
return message;
- }
+ }
}
return null;
}
@@ -467,23 +460,7 @@ public class NotificationService {
}
private boolean wasHighlightedOrPrivate(final Message message) {
- final String nick = message.getConversation().getMucOptions().getActualNick();
- final Pattern highlight = generateNickHighlightPattern(nick);
- if (message.getBody() == null || nick == null) {
- return false;
- }
- final Matcher m = highlight.matcher(message.getBody());
- return (m.find() || message.getType() == Message.TYPE_PRIVATE);
- }
-
- private static Pattern generateNickHighlightPattern(final String nick) {
- // We expect a word boundary, i.e. space or start of string, followed by
- // the
- // nick (matched in case-insensitive manner), followed by optional
- // punctuation (for example "bob: i disagree" or "how are you alice?"),
- // followed by another word boundary.
- return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
- Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
+ return MessageUtil.wasHighlightedOrPrivate(message);
}
public void setOpenConversation(final Conversation conversation) {
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 2159e92e..1f537172 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -47,7 +47,6 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -59,6 +58,15 @@ import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.exceptions.FileCopyException;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -89,10 +97,10 @@ import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.ExceptionHelper;
+import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
import eu.siacs.conversations.utils.PRNGFixes;
import eu.siacs.conversations.utils.PhoneHelper;
-import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnBindListener;
@@ -113,7 +121,6 @@ import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
-import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
@@ -128,11 +135,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh";
public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received";
- private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
- private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
private final IBinder mBinder = new XmppConnectionBinder();
private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
- private final IqGenerator mIqGenerator = new IqGenerator(this);
+ private final IqGenerator mIqGenerator = new IqGenerator();
private final List<String> mInProgressAvatarFetches = new ArrayList<>();
public DatabaseBackend databaseBackend;
private ContentObserver contactObserver = new ContentObserver(null) {
@@ -145,7 +150,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
startService(intent);
}
};
- private FileBackend fileBackend = new FileBackend(this);
private MemorizingTrustManager mMemorizingTrustManager;
private NotificationService mNotificationService = new NotificationService(
this);
@@ -159,13 +163,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Element error = packet.findChild("error");
String text = error != null ? error.findChildContent("text") : null;
if (text != null) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text);
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text);
}
}
}
};
- private MessageGenerator mMessageGenerator = new MessageGenerator(this);
- private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
+ private MessageGenerator mMessageGenerator = new MessageGenerator();
+ private PresenceGenerator mPresenceGenerator = new PresenceGenerator();
private List<Account> accounts;
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
this);
@@ -197,7 +201,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
};
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
this);
- private AvatarService mAvatarService = new AvatarService(this);
private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
private PushManagementService mPushManagementService = new PushManagementService(this);
private OnConversationUpdate mOnConversationUpdate = null;
@@ -286,10 +289,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mMessageArchiveService.executePendingQueries(account);
if (connection != null && connection.getFeatures().csi()) {
if (checkListeners()) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive");
connection.sendInactive();
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active");
connection.sendActive();
}
}
@@ -298,7 +301,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getAccount() == account
&& !account.pendingConferenceJoins.contains(conversation)) {
if (!conversation.startOtrIfNeeded()) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": couldn't start OTR with "+conversation.getContact().getJid()+" when needed");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": couldn't start OTR with "+conversation.getContact().getJid()+" when needed");
}
sendUnsentMessages(conversation);
}
@@ -318,7 +321,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final boolean pushMode = Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND
&& mPushManagementService.available(account)
&& checkListeners();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode));
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode));
if (!disabled && !pushMode) {
int timeToReconnect = mRandom.nextInt(20) + 10;
scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
@@ -330,7 +333,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
&& (account.getStatus() != Account.State.NO_INTERNET)) {
if (connection != null) {
int next = connection.getTimeToNextAttempt();
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": error connecting account. try again in "
+ next + "s for the "
+ (connection.getAttempt() + 1) + " time");
@@ -350,10 +353,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private boolean mRestoredFromDatabase = false;
- private static String generateFetchKey(Account account, final Avatar avatar) {
- return account.getJid().toBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum;
- }
-
public boolean areMessagesInitialized() {
return this.mRestoredFromDatabase;
}
@@ -371,15 +370,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else {
return null;
}
-
- }
-
- public FileBackend getFileBackend() {
- return this.fileBackend;
- }
-
- public AvatarService getAvatarService() {
- return this.mAvatarService;
}
public void attachLocationToConversation(final Conversation conversation,
@@ -411,67 +401,33 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_FILE);
- String path = getFileBackend().getOriginalPath(uri);
+ String path = FileUtils.getPath(uri);
if (path != null) {
message.setRelativeFilePath(path);
- getFileBackend().updateFileParams(message);
+ MessageUtil.updateFileParams(message);
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
getPgpEngine().encrypt(message, callback);
} else {
callback.success(message);
}
} else {
- mFileAddingExecutor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- getFileBackend().copyFileToPrivateStorage(message, uri);
- getFileBackend().updateFileParams(message);
- if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- getPgpEngine().encrypt(message, callback);
- } else {
- callback.success(message);
- }
- } catch (FileBackend.FileCopyException e) {
- callback.error(e.getResId(), message);
- }
- }
- });
- }
- }
-
- public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
- final String compressPictures = getCompressPicturesPreference();
- if ("never".equals(compressPictures)
- || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri))) {
- Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+ ": not compressing picture. sending as file");
- attachFileToConversation(conversation, uri, callback);
- return;
- }
- final Message message;
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
- } else {
- message = new Message(conversation, "", conversation.getNextEncryption());
+ ConversationsPlusApplication.executeFileAdding(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FileBackend.copyFileToPrivateStorage(message, uri);
+ MessageUtil.updateFileParams(message);
+ if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ getPgpEngine().encrypt(message, callback);
+ } else {
+ callback.success(message);
+ }
+ } catch (FileCopyException e) {
+ callback.error(e.getResId(), message);
+ }
+ }
+ });
}
- message.setCounterpart(conversation.getNextCounterpart());
- message.setType(Message.TYPE_IMAGE);
- mFileAddingExecutor.execute(new Runnable() {
-
- @Override
- public void run() {
- try {
- getFileBackend().copyImageToPrivateStorage(message, uri);
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- getPgpEngine().encrypt(message, callback);
- } else {
- callback.success(message);
- }
- } catch (final FileBackend.FileCopyException e) {
- callback.error(e.getResId(), message);
- }
- }
- });
}
public Conversation find(Bookmark bookmark) {
@@ -505,7 +461,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mNotificationService.clear();
break;
case ACTION_DISABLE_FOREGROUND:
- getPreferences().edit().putBoolean("keep_foreground_service", false).commit();
+ ConversationsPlusPreferences.commitKeepForegroundService(false);
toggleForegroundService();
break;
case ACTION_TRY_AGAIN:
@@ -566,7 +522,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime();
if (lastSent > lastReceived) {
if (pingTimeoutIn < 0) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
this.reconnectAccount(account, true, interactive);
} else {
int secs = (int) (pingTimeoutIn / 1000);
@@ -574,7 +530,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
} else if (msToNextPing <= 0) {
account.getXmppConnection().sendPing();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping");
this.scheduleWakeUpCall(Config.PING_TIMEOUT, account.getUuid().hashCode());
} else {
this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
@@ -587,7 +543,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
if (timeout < 0) {
- Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
+ Logging.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
reconnectAccount(account, true, interactive);
} else if (discoTimeout < 0) {
account.getXmppConnection().sendDiscoTimeout();
@@ -600,6 +556,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
reconnectAccount(account, true, interactive);
}
}
+
}
if (mOnAccountUpdate != null) {
mOnAccountUpdate.onAccountUpdate();
@@ -627,10 +584,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("away_when_screen_off", false);
}
- private String getCompressPicturesPreference() {
- return getPreferences().getString("picture_compression", "auto");
- }
-
private Presence.Status getTargetPresence() {
if (xaOnSilentMode() && isPhoneSilenced()) {
return Presence.Status.XA;
@@ -665,7 +618,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
private void resetAllAttemptCounts(boolean reallyAll) {
- Log.d(Config.LOGTAG, "resetting all attempt counts");
+ Logging.d(Config.LOGTAG, "resetting all attempt counts");
for (Account account : accounts) {
if (account.hasErrorStatus() || reallyAll) {
final XmppConnection connection = account.getXmppConnection();
@@ -683,6 +636,25 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return activeNetwork != null && activeNetwork.isConnected();
}
+ /**
+ * check whether we are allowed to download at the moment
+ */
+ public boolean isDownloadAllowedInConnection() {
+ if (ConversationsPlusPreferences.autoDownloadFileWLAN()) {
+ return isWifiConnected();
+ }
+ return true;
+ }
+
+ /**
+ * check whether wifi is connected
+ */
+ public boolean isWifiConnected() {
+ ConnectivityManager cm = (ConnectivityManager) ConversationsPlusApplication.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo niWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ return niWifi.isConnected();
+ }
+
@SuppressLint("TrulyRandom")
@Override
public void onCreate() {
@@ -729,6 +701,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService");
toggleForegroundService();
updateUnreadCountBadge();
+ UiUpdateHelper.initXmppConnectionService(this);
toggleScreenEventReceiver();
}
@@ -766,7 +739,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void toggleForegroundService() {
- if (getPreferences().getBoolean("keep_foreground_service", false)) {
+ if (ConversationsPlusPreferences.keepForegroundService()) {
startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification());
} else {
stopForeground(true);
@@ -776,7 +749,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- if (!getPreferences().getBoolean("keep_foreground_service", false)) {
+ if (!ConversationsPlusPreferences.keepForegroundService()) {
this.logoutAndSave();
}
}
@@ -794,7 +767,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
cancelWakeUpCall(account.getUuid().hashCode());
}
}
- Log.d(Config.LOGTAG, "good bye");
+ Logging.d(Config.LOGTAG, "good bye");
stopSelf();
}
@@ -818,9 +791,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public XmppConnection createConnection(final Account account) {
- final SharedPreferences sharedPref = getPreferences();
- account.setResource(sharedPref.getString("resource", "mobile")
- .toLowerCase(Locale.getDefault()));
+ account.setResource(ConversationsPlusPreferences.resource().toLowerCase(Locale.getDefault()));
final XmppConnection connection = new XmppConnection(account, this);
connection.setOnMessagePacketReceivedListener(this.mMessageParser);
connection.setOnStatusChangedListener(this.statusListener);
@@ -838,14 +809,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void sendChatState(Conversation conversation) {
- if (sendChatStates()) {
+ if (ConversationsPlusPreferences.chatStates()) {
MessagePacket packet = mMessageGenerator.generateChatState(conversation);
sendMessagePacket(conversation.getAccount(), packet);
}
}
private void sendFileMessage(final Message message, final boolean delay) {
- Log.d(Config.LOGTAG, "send file message");
+ Logging.d(Config.LOGTAG, "send file message");
final Account account = message.getConversation().getAccount();
final XmppConnection connection = account.getXmppConnection();
if (connection != null && connection.getFeatures().httpUpload()) {
@@ -923,11 +894,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (message.fixCounterpart()) {
conversation.startOtrSession(message.getCounterpart().getResourcepart(), true);
} else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not fix counterpart for OTR message to contact "+message.getContact().getJid());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not fix counterpart for OTR message to contact "+message.getContact().getJid());
break;
}
} else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+" OTR session with "+message.getContact()+" is in wrong state: "+otrSession.getSessionStatus().toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+" OTR session with "+message.getContact()+" is in wrong state: "+otrSession.getSessionStatus().toString());
}
break;
case Message.ENCRYPTION_AXOLOTL:
@@ -972,7 +943,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
break;
case Message.ENCRYPTION_OTR:
if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": create otr session without starting for "+message.getContact().getJid());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": create otr session without starting for "+message.getContact().getJid());
conversation.startOtrSession(message.getCounterpart().getResourcepart(), false);
}
break;
@@ -1008,7 +979,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mMessageGenerator.addDelay(packet, message.getTimeSent());
}
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
- if (this.sendChatStates()) {
+ if (ConversationsPlusPreferences.chatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
}
}
@@ -1019,11 +990,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private void sendUnsentMessages(final Conversation conversation) {
conversation.findWaitingMessages(new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- resendMessage(message, true);
- }
- });
+ @Override
+ public void onMessageFound(Message message) {
+ resendMessage(message, true);
+ }
+ });
}
public void resendMessage(final Message message, final boolean delay) {
@@ -1033,10 +1004,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void fetchRosterFromServer(final Account account) {
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
if (!"".equals(account.getRosterVersion())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": fetching roster version " + account.getRosterVersion());
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
}
iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion());
sendIqPacket(account, iqPacket, mIqParser);
@@ -1077,7 +1048,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
account.setBookmarks(new ArrayList<>(bookmarks.values()));
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks");
}
}
};
@@ -1085,7 +1056,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void pushBookmarks(Account account) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks");
IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks");
@@ -1102,12 +1073,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mPhoneContactMergerThread = new Thread(new Runnable() {
@Override
public void run() {
- Log.d(Config.LOGTAG, "start merging phone contacts with roster");
+ Logging.d(Config.LOGTAG, "start merging phone contacts with roster");
for (Account account : accounts) {
List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts();
for (Bundle phoneContact : phoneContacts) {
if (Thread.interrupted()) {
- Log.d(Config.LOGTAG, "interrupted merging phone contacts");
+ Logging.d(Config.LOGTAG,"interrupted merging phone contacts");
return;
}
Jid jid;
@@ -1122,7 +1093,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
+ phoneContact.getString("lookup");
contact.setSystemAccount(systemAccount);
if (contact.setPhotoUri(phoneContact.getString("photouri"))) {
- getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
}
contact.setSystemName(phoneContact.getString("displayname"));
withSystemAccounts.remove(contact);
@@ -1131,11 +1102,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
contact.setSystemAccount(null);
contact.setSystemName(null);
if (contact.setPhotoUri(null)) {
- getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
}
}
}
- Log.d(Config.LOGTAG, "finished merging phone contacts");
+ Logging.d(Config.LOGTAG,"finished merging phone contacts");
updateAccountUi();
}
});
@@ -1156,15 +1127,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Runnable runnable = new Runnable() {
@Override
public void run() {
- Log.d(Config.LOGTAG, "restoring roster");
+ Logging.d(Config.LOGTAG, "restoring roster");
for (Account account : accounts) {
databaseBackend.readRoster(account.getRoster());
account.initAccountServices(XmppConnectionService.this); //roster needs to be loaded at this stage
}
- getBitmapCache().evictAll();
+ ImageUtil.evictBitmapCache();
Looper.prepare();
loadPhoneContacts();
- Log.d(Config.LOGTAG, "restoring messages");
+ Logging.d(Config.LOGTAG, "restoring messages");
for (Conversation conversation : conversations) {
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
checkDeletedFiles(conversation);
@@ -1177,11 +1148,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
mNotificationService.finishBacklog(false);
mRestoredFromDatabase = true;
- Log.d(Config.LOGTAG, "restored all messages");
+ Logging.d(Config.LOGTAG,"restored all messages");
updateConversationUi();
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
}
@@ -1200,7 +1171,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onMessageFound(Message message) {
- if (!getFileBackend().isFileAvailable(message)) {
+ if (!FileBackend.isFileAvailable(message)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
final int s = message.getStatus();
if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
@@ -1215,7 +1186,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Conversation conversation : getConversations()) {
Message message = conversation.findMessageWithFileAndUuid(uuid);
if (message != null) {
- if (!getFileBackend().isFileAvailable(message)) {
+ if (!FileBackend.isFileAvailable(message)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
final int s = message.getStatus();
if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
@@ -1246,52 +1217,72 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
Collections.sort(list, new Comparator<Conversation>() {
- @Override
- public int compare(Conversation lhs, Conversation rhs) {
- Message left = lhs.getLatestMessage();
- Message right = rhs.getLatestMessage();
- if (left.getTimeSent() > right.getTimeSent()) {
- return -1;
- } else if (left.getTimeSent() < right.getTimeSent()) {
- return 1;
- } else {
- return 0;
- }
- }
- });
+ @Override
+ public int compare(Conversation lhs, Conversation rhs) {
+ Message left = lhs.getLatestMessage();
+ Message right = rhs.getLatestMessage();
+ if (left.getTimeSent() > right.getTimeSent()) {
+ return -1;
+ } else if (left.getTimeSent() < right.getTimeSent()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ });
}
public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) {
if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) {
+ Logging.d("mam", "Query in progress");
return;
} else if (timestamp == 0) {
+ Logging.d("mam", "Query stopped due to timestamp");
return;
}
- Log.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp));
+ //TODO Create a separate class for this runnable to store if messages are getting loaded or not. Not really a good idea to do this in the callback.
+ Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp));
Runnable runnable = new Runnable() {
@Override
public void run() {
+ if (null == callback || !callback.isLoadingInProgress()) { // if a callback is set, ensure that there is no loading in progress
+ if (null != callback) {
+ callback.setLoadingInProgress(); // Tell the callback that the loading is in progress
+ }
final Account account = conversation.getAccount();
List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp);
+ Logging.d("mam", "runnable load more messages");
if (messages.size() > 0) {
+ Logging.d("mam", "At least one message");
conversation.addAll(0, messages);
checkDeletedFiles(conversation);
callback.onMoreMessagesLoaded(messages.size(), conversation);
} else if (conversation.hasMessagesLeftOnServer()
- && account.isOnlineAndConnected()
- && conversation.getLastClearHistory() == 0) {
+ && account.isOnlineAndConnected()) {
+ Logging.d("mam", "account online and connected and messages left on server");
+ //TODO Check if this needs to be checked before trying anything with regards to MAM
if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam())
|| (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) {
- MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp);
- if (query != null) {
- query.setCallback(callback);
- }
+ Logging.d("mam", "mam active");
+ getMessageArchiveService().query(conversation, 0, timestamp - 1, callback);
callback.informUser(R.string.fetching_history_from_server);
- }
- }
- }
+ } else {
+ Logging.d("mam", "mam inactive");
+ callback.onMoreMessagesLoaded(0, conversation);
+ }
+ } else {
+ Logging.d("mam", ((!conversation.hasMessagesLeftOnServer()) ? "no" : "")
+ + " more messages left on server, mam "
+ + ((account.isOnlineAndConnected() && account.getXmppConnection().getFeatures().mam()) ? "" : "not")
+ + " activated, account is " + ((account.isOnlineAndConnected()) ? "" : "not")
+ + " online or connected)");
+ callback.onMoreMessagesLoaded(0, conversation);
+ callback.informUser(R.string.no_more_history_on_server);
+ }
+ }
+ }
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public List<Account> getAccounts() {
@@ -1520,7 +1511,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.deleteAccount(account);
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
this.accounts.remove(account);
updateAccountUi();
getNotificationService().updateErrorNotification();
@@ -1752,7 +1743,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- Log.d(Config.LOGTAG, "app switched into foreground");
+ Logging.d(Config.LOGTAG, "app switched into foreground");
}
private void switchToBackground() {
@@ -1771,7 +1762,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
this.mNotificationService.setIsInForeground(false);
- Log.d(Config.LOGTAG, "app switched into background");
+ Logging.d(Config.LOGTAG, "app switched into background");
}
private void connectMultiModeConversations(Account account) {
@@ -1968,7 +1959,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendPresencePacket(conversation.getAccount(), packet);
conversation.getMucOptions().setOffline();
conversation.deregisterWithBookmark();
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
+ Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
+ ": leaving muc " + conversation.getJid());
} else {
account.pendingConferenceLeaves.add(conversation);
@@ -1995,7 +1986,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void createAdhocConference(final Account account, final Iterable<Jid> jids, final UiCallback<Conversation> callback) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString());
if (account.getStatus() == Account.State.ONLINE) {
try {
String server = findConferenceServer(account);
@@ -2081,7 +2072,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (callback != null) {
callback.onConferenceConfigurationFetched(conversation);
}
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetched muc configuration for "+conversation.getJid().toBareJid()+" - "+features.toString());
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetched muc configuration for " + conversation.getJid().toBareJid() + " - " + features.toString());
updateConversationUi();
} else if (packet.getType() == IqPacket.TYPE.ERROR) {
if (callback != null) {
@@ -2171,11 +2162,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) {
IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString());
- Log.d(Config.LOGTAG, request.toString());
+ Logging.d(Config.LOGTAG, request.toString());
sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- Log.d(Config.LOGTAG, packet.toString());
+ Logging.d(Config.LOGTAG, packet.toString());
if (packet.getType() == IqPacket.TYPE.RESULT) {
callback.onRoleChangedSuccessful(nick);
} else {
@@ -2196,7 +2187,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
leaveMuc(conversation, true);
} else {
if (conversation.endOtrIfNeeded()) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": ended otr session with "
+ conversation.getJid());
}
@@ -2236,8 +2227,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void createContact(Contact contact) {
- boolean autoGrant = getPreferences().getBoolean("grant_new_contacts", true);
- if (autoGrant) {
+ if (ConversationsPlusPreferences.grantNewContacts()) {
contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
contact.setOption(Contact.Options.ASKING);
}
@@ -2247,10 +2237,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onOtrSessionEstablished(Conversation conversation) {
final Account account = conversation.getAccount();
final Session otrSession = conversation.getOtrSession();
- Log.d(Config.LOGTAG,
- account.getJid().toBareJid() + " otr session established with "
- + conversation.getJid() + "/"
- + otrSession.getSessionID().getUserID());
+ Logging.d(Config.LOGTAG,
+ account.getJid().toBareJid() + " otr session established with "
+ + conversation.getJid() + "/"
+ + otrSession.getSessionID().getUserID());
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
@Override
@@ -2288,7 +2278,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
packet.setFrom(account.getJid());
MessageGenerator.addMessageHints(packet);
packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
- + otrSession.getSessionID().getUserID());
+ + otrSession.getSessionID().getUserID());
try {
packet.setBody(otrSession
.transformSending(CryptoHelper.FILETRANSFER
@@ -2326,221 +2316,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public void publishAvatar(final Account account,
- final Uri image,
- final UiCallback<Avatar> callback) {
- final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
- final int size = Config.AVATAR_SIZE;
- final Avatar avatar = getFileBackend().getPepAvatar(image, size, format);
- if (avatar != null) {
- avatar.height = size;
- avatar.width = size;
- if (format.equals(Bitmap.CompressFormat.WEBP)) {
- avatar.type = "image/webp";
- } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
- avatar.type = "image/jpeg";
- } else if (format.equals(Bitmap.CompressFormat.PNG)) {
- avatar.type = "image/png";
- }
- if (!getFileBackend().save(avatar)) {
- callback.error(R.string.error_saving_avatar, avatar);
- return;
- }
- final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- final IqPacket packet = XmppConnectionService.this.mIqGenerator
- .publishAvatarMetadata(avatar);
- sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- if (account.setAvatar(avatar.getFilename())) {
- getAvatarService().clear(account);
- databaseBackend.updateAccount(account);
- }
- callback.success(avatar);
- } else {
- callback.error(
- R.string.error_publish_avatar_server_reject,
- avatar);
- }
- }
- });
- } else {
- callback.error(
- R.string.error_publish_avatar_server_reject,
- avatar);
- }
- }
- });
- } else {
- callback.error(R.string.error_publish_avatar_converting, null);
- }
- }
-
- public void fetchAvatar(Account account, Avatar avatar) {
- fetchAvatar(account, avatar, null);
- }
-
- public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- final String KEY = generateFetchKey(account, avatar);
- synchronized (this.mInProgressAvatarFetches) {
- if (this.mInProgressAvatarFetches.contains(KEY)) {
- return;
- } else {
- switch (avatar.origin) {
- case PEP:
- this.mInProgressAvatarFetches.add(KEY);
- fetchAvatarPep(account, avatar, callback);
- break;
- case VCARD:
- this.mInProgressAvatarFetches.add(KEY);
- fetchAvatarVcard(account, avatar, callback);
- break;
- }
- }
- }
- }
-
- private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar);
- sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- synchronized (mInProgressAvatarFetches) {
- mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
- }
- final String ERROR = account.getJid().toBareJid()
- + ": fetching avatar for " + avatar.owner + " failed ";
- if (result.getType() == IqPacket.TYPE.RESULT) {
- avatar.image = mIqParser.avatarData(result);
- if (avatar.image != null) {
- if (getFileBackend().save(avatar)) {
- if (account.getJid().toBareJid().equals(avatar.owner)) {
- if (account.setAvatar(avatar.getFilename())) {
- databaseBackend.updateAccount(account);
- }
- getAvatarService().clear(account);
- updateConversationUi();
- updateAccountUi();
- } else {
- Contact contact = account.getRoster()
- .getContact(avatar.owner);
- contact.setAvatar(avatar);
- getAvatarService().clear(contact);
- updateConversationUi();
- updateRosterUi();
- }
- if (callback != null) {
- callback.success(avatar);
- }
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": succesfuly fetched pep avatar for " + avatar.owner);
- return;
- }
- } else {
-
- Log.d(Config.LOGTAG, ERROR + "(parsing error)");
- }
- } else {
- Element error = result.findChild("error");
- if (error == null) {
- Log.d(Config.LOGTAG, ERROR + "(server error)");
- } else {
- Log.d(Config.LOGTAG, ERROR + error.toString());
- }
- }
- if (callback != null) {
- callback.error(0, null);
- }
-
- }
- });
- }
-
- private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- synchronized (mInProgressAvatarFetches) {
- mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
- }
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element vCard = packet.findChild("vCard", "vcard-temp");
- Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
- String image = photo != null ? photo.findChildContent("BINVAL") : null;
- if (image != null) {
- avatar.image = image;
- if (getFileBackend().save(avatar)) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": successfully fetched vCard avatar for " + avatar.owner);
- if (avatar.owner.isBareJid()) {
- Contact contact = account.getRoster()
- .getContact(avatar.owner);
- contact.setAvatar(avatar);
- getAvatarService().clear(contact);
- updateConversationUi();
- updateRosterUi();
- } else {
- Conversation conversation = find(account, avatar.owner.toBareJid());
- if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
- MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart());
- if (user != null) {
- if (user.setAvatar(avatar)) {
- getAvatarService().clear(user);
- updateConversationUi();
- updateMucRosterUi();
- }
- }
- }
- }
- }
- }
- }
- }
- });
- }
-
- public void checkForAvatar(Account account, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element pubsub = packet.findChild("pubsub",
- "http://jabber.org/protocol/pubsub");
- if (pubsub != null) {
- Element items = pubsub.findChild("items");
- if (items != null) {
- Avatar avatar = Avatar.parseMetadata(items);
- if (avatar != null) {
- avatar.owner = account.getJid().toBareJid();
- if (fileBackend.isAvatarCached(avatar)) {
- if (account.setAvatar(avatar.getFilename())) {
- databaseBackend.updateAccount(account);
- }
- getAvatarService().clear(account);
- callback.success(avatar);
- } else {
- fetchAvatarPep(account, avatar, callback);
- }
- return;
- }
- }
- }
- }
- callback.error(0, null);
- }
- });
- }
-
public void deleteContactOnServer(Contact contact) {
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
contact.resetOption(Contact.Options.DIRTY_PUSH);
@@ -2570,7 +2345,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (!force) {
disconnect(account, false);
try {
- Log.d(Config.LOGTAG, "wait for disconnect");
+ Logging.d(Config.LOGTAG, "wait for disconnect");
Thread.sleep(500); //sleep wait for disconnect
} catch (InterruptedException e) {
//ignored
@@ -2599,7 +2374,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void invite(Conversation conversation, Jid contact) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": inviting " + contact + " to " + conversation.getJid().toBareJid());
+ Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": inviting " + contact + " to " + conversation.getJid().toBareJid());
MessagePacket packet = mMessageGenerator.invite(conversation, contact);
sendMessagePacket(conversation.getAccount(), packet);
}
@@ -2669,18 +2444,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
.getDefaultSharedPreferences(getApplicationContext());
}
- public boolean confirmMessages() {
- return getPreferences().getBoolean("confirm_messages", true);
- }
-
- public boolean allowMessageCorrection() {
- return getPreferences().getBoolean("allow_message_correction", false);
- }
-
- public boolean sendChatStates() {
- return getPreferences().getBoolean("chat_states", false);
- }
-
public boolean saveEncryptedMessages() {
return !getPreferences().getBoolean("dont_save_encrypted", false);
}
@@ -2689,14 +2452,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("autojoin", true);
}
- public boolean indicateReceived() {
- return getPreferences().getBoolean("indicate_received", false);
- }
-
- public boolean useTorToConnect() {
- return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false);
- }
-
public boolean showExtendedConnectionOptions() {
return getPreferences().getBoolean("show_connection_options", false);
}
@@ -2796,7 +2551,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
updateUnreadCountBadge();
return true;
} else {
@@ -2807,7 +2562,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public synchronized void updateUnreadCountBadge() {
int count = unreadCount();
if (unreadCount != count) {
- Log.d(Config.LOGTAG, "update unread count to " + count);
+ Logging.d(Config.LOGTAG, "update unread count to " + count);
if (count > 0) {
ShortcutBadger.with(getApplicationContext()).count(count);
} else {
@@ -2822,8 +2577,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (this.markRead(conversation)) {
updateConversationUi();
}
- if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString());
+ if (Settings.CONFIRM_MESSAGE_READ && markable != null && markable.getRemoteMsgId() != null) {
+ Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString());
Account account = conversation.getAccount();
final Jid to = markable.getCounterpart();
MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId());
@@ -2845,9 +2600,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void updateMemorizingTrustmanager() {
final MemorizingTrustManager tm;
- final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", false);
- if (dontTrustSystemCAs) {
- tm = new MemorizingTrustManager(getApplicationContext(), null);
+ if (ConversationsPlusPreferences.dontTrustSystemCAs()) {
+ tm = new MemorizingTrustManager(getApplicationContext(), null);
} else {
tm = new MemorizingTrustManager(getApplicationContext());
}
@@ -2870,8 +2624,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.writeRoster(account.getRoster());
}
};
- mDatabaseExecutor.execute(runnable);
-
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public List<String> getKnownHosts() {
@@ -2905,18 +2658,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return mucServers;
}
+ @Deprecated
public void sendMessagePacket(Account account, MessagePacket packet) {
- XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.sendMessagePacket(packet);
- }
+ XmppSendUtil.sendMessagePacket(account, packet);
}
+ @Deprecated
public void sendPresencePacket(Account account, PresencePacket packet) {
- XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.sendPresencePacket(packet);
- }
+ XmppSendUtil.sendPresencePacket(account, packet);
}
public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) {
@@ -2926,15 +2675,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ @Deprecated
public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) {
- final XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.sendIqPacket(packet, callback);
- }
+ XmppSendUtil.sendIqPacket(account, packet, callback);
}
public void sendPresence(final Account account) {
- sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence()));
+ XmppSendUtil.sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence()));
}
public void refreshAllPresences() {
@@ -2954,7 +2701,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void sendOfflinePresence(final Account account) {
- sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
+ XmppSendUtil.sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
}
public MessageGenerator getMessageGenerator() {
@@ -3003,34 +2750,28 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void resendFailedMessages(final Message message) {
- final Collection<Message> messages = new ArrayList<>();
- Message current = message;
- while (current.getStatus() == Message.STATUS_SEND_FAILED) {
- messages.add(current);
- if (current.mergeable(current.next())) {
- current = current.next();
- } else {
- break;
- }
- }
- for (final Message msg : messages) {
- msg.setTime(System.currentTimeMillis());
- markMessage(msg, Message.STATUS_WAITING);
- this.resendMessage(msg, false);
- }
+ if (message.getStatus() == Message.STATUS_SEND_FAILED) {
+ message.setTime(System.currentTimeMillis());
+ markMessage(message, Message.STATUS_WAITING);
+ this.resendMessage(message, false);
+ }
}
public void clearConversationHistory(final Conversation conversation) {
conversation.clearMessages();
- conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam
- conversation.setLastClearHistory(System.currentTimeMillis());
+ /*
+ * In case the history was loaded completely before.
+ * The flag "hasMessagesLeftOnServer" is set to false and no messages will be loaded anymore
+ * Therefore set this flag to true and try to get messages from server
+ */
+ conversation.setHasMessagesLeftOnServer(true);
Runnable runnable = new Runnable() {
@Override
public void run() {
databaseBackend.deleteMessagesInConversation(conversation);
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public void sendBlockRequest(final Blockable blockable) {
@@ -3072,7 +2813,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.ERROR) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not publish nick");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not publish nick");
}
}
});
@@ -3113,7 +2854,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.insertDiscoveryResult(disco);
injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco);
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer());
}
}
account.inProgressDiscoFetches.remove(key);
@@ -3174,6 +2915,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
void onMoreMessagesLoaded(int count, Conversation conversation);
void informUser(int r);
+
+ void setLoadingInProgress();
+
+ boolean isLoadingInProgress();
}
public interface OnAccountPasswordChanged {
diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
index bd2042fb..b9e5c367 100644
--- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
+++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
@@ -5,7 +5,7 @@ import android.content.Intent;
import android.preference.Preference;
import android.util.AttributeSet;
-import eu.siacs.conversations.utils.PhoneHelper;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
public class AboutPreference extends Preference {
public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
@@ -26,7 +26,7 @@ public class AboutPreference extends Preference {
}
private void setSummary() {
- setSummary("Conversations " + PhoneHelper.getVersionName(getContext()));
+ setSummary(ConversationsPlusApplication.getNameAndVersion());
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
index ccb3a22e..1cc8fd29 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
@@ -9,8 +9,6 @@ import android.widget.Toast;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.xmpp.jid.InvalidJidException;
-import eu.siacs.conversations.xmpp.jid.Jid;
public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged {
@@ -26,11 +24,14 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti
mCurrentPassword.requestFocus();
mCurrentPassword.setError(getString(R.string.account_status_unauthorized));
} else if (!newPassword.equals(newPasswordConfirm)) {
- mNewPasswordConfirm.requestFocus();
- mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
+ mNewPasswordConfirm.requestFocus();
+ mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
+ } else if (newPassword.isEmpty()) {
+ mNewPassword.requestFocus();
+ mNewPassword.setError(getString(R.string.password_should_not_be_empty));
} else if (newPassword.trim().isEmpty()) {
mNewPassword.requestFocus();
- mNewPassword.setError(getString(R.string.password_should_not_be_empty));
+ mNewPassword.setError(getString(R.string.password_should_not_contain_only_spaces));
} else {
mCurrentPassword.setError(null);
mNewPassword.setError(null);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index adbb0953..dec387b6 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -38,6 +38,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.User;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
@@ -509,7 +510,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
account = mConversation.getAccount().getJid().toBareJid().toString();
}
mAccountJid.setText(getString(R.string.using_account, account));
- mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48)));
+ mYourPhoto.setImageBitmap(AvatarService.getInstance().get(mConversation.getAccount(), getPixel(48)));
setTitle(mConversation.getName());
if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && mConversation.getJid().getDomainpart().equals(Config.CONFERENCE_DOMAIN_LOCK)) {
mFullJid.setText(mConversation.getJid().getLocalpart());
@@ -603,7 +604,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
ImageView iv = (ImageView) view.findViewById(R.id.contact_photo);
- iv.setImageBitmap(avatarService().get(user, getPixel(48), false));
+ iv.setImageBitmap(AvatarService.getInstance().get(user, getPixel(48), false));
membersView.addView(view);
if (mConversation.getMucOptions().canInvite()) {
mInviteButton.setVisibility(View.VISIBLE);
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index e4fc59fa..b11564a9 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -6,10 +6,8 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
-import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
@@ -34,6 +32,8 @@ import org.openintents.openpgp.util.OpenPgpUtils;
import java.security.cert.X509Certificate;
import java.util.List;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -42,6 +42,7 @@ import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -220,8 +221,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
getActionBar().setDisplayHomeAsUpEnabled(true);
}
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
+ this.showDynamicTags = ConversationsPlusPreferences.showDynamicTags();
}
@Override
@@ -369,8 +369,9 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
} else {
account = contact.getAccount().getJid().toBareJid().toString();
}
+ contactJidTv.setOnClickListener(new ShowResourcesListDialogListener(ContactDetailsActivity.this, contact));
accountJidTv.setText(getString(R.string.using_account, account));
- badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
+ badge.setImageBitmap(AvatarService.getInstance().get(contact, getPixel(72)));
badge.setOnClickListener(this.onBadgeClick);
keys.removeAllViews();
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index bb85439a..175df041 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -43,10 +43,14 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog;
+import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener;
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.crypto.axolotl.AxolotlServiceImpl;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
@@ -54,6 +58,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
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.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
@@ -273,6 +278,7 @@ public class ConversationActivity extends XmppActivity
}
});
listView.enableSwipeToDismiss();
+ listView.setSwipeDirection(EnhancedListView.SwipeDirection.START);
listView.setSwipingLayout(R.id.swipeable_item);
listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
listView.setUndoHideDelay(5000);
@@ -284,9 +290,10 @@ public class ConversationActivity extends XmppActivity
}
if (mContentView instanceof SlidingPaneLayout) {
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ // Move the conversation list when sliding the selected conversation
mSlidingPaneLayout.setParallaxDistance(150);
- mSlidingPaneLayout
- .setShadowResource(R.drawable.es_slidingpane_shadow);
+ // The shadow between conversation list and selected conversation
+ mSlidingPaneLayout.setShadowResourceLeft(R.drawable.es_slidingpane_shadow);
mSlidingPaneLayout.setSliderFadeColor(0);
mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
@@ -340,7 +347,7 @@ public class ConversationActivity extends XmppActivity
if (titleShouldBeName && conversation != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setHomeButtonEnabled(true);
- if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) {
+ if (conversation.getMode() == Conversation.MODE_SINGLE || ConversationsPlusPreferences.useSubject()) {
ab.setTitle(conversation.getName());
} else {
ab.setTitle(conversation.getJid().toBareJid().toString());
@@ -443,7 +450,7 @@ public class ConversationActivity extends XmppActivity
chooser = true;
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
- Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
+ Uri uri = FileBackend.getTakePhotoUri();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
mPendingImageUris.clear();
@@ -504,16 +511,16 @@ public class ConversationActivity extends XmppActivity
}
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_LOCATION:
- getPreferences().edit().putString("recently_used_quick_action", "location").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("location");
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
- getPreferences().edit().putString("recently_used_quick_action", "voice").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("voice");
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
- getPreferences().edit().putString("recently_used_quick_action", "photo").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("photo");
break;
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
- getPreferences().edit().putString("recently_used_quick_action", "picture").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("picture");
break;
}
final Conversation conversation = getSelectedConversation();
@@ -829,7 +836,7 @@ public class ConversationActivity extends XmppActivity
}
break;
case R.id.encryption_choice_axolotl:
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(conversation.getAccount())
+ "Enabled axolotl for Contact " + conversation.getContact().getJid());
conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
item.setChecked(true);
@@ -1070,7 +1077,7 @@ public class ConversationActivity extends XmppActivity
public void onResume() {
super.onResume();
final int theme = findTheme();
- final boolean usingEnterKey = usingEnterKey();
+ final boolean usingEnterKey = ConversationsPlusPreferences.displayEnterKey();
if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) {
recreate();
}
@@ -1155,7 +1162,6 @@ public class ConversationActivity extends XmppActivity
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
} else {
- this.mConversationFragment.messageListAdapter.updatePreferences();
this.mConversationFragment.messagesView.invalidateViews();
this.mConversationFragment.setupIme();
}
@@ -1443,28 +1449,9 @@ public class ConversationActivity extends XmppActivity
if (conversation == null) {
return;
}
- final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG);
- prepareFileToast.show();
- xmppConnectionService.attachImageToConversation(conversation, uri,
- new UiCallback<Message>() {
-
- @Override
- public void userInputRequried(PendingIntent pi, Message object) {
- hidePrepareFileToast(prepareFileToast);
- }
-
- @Override
- public void success(Message message) {
- hidePrepareFileToast(prepareFileToast);
- xmppConnectionService.sendMessage(message);
- }
-
- @Override
- public void error(int error, Message message) {
- hidePrepareFileToast(prepareFileToast);
- displayErrorDialog(error);
- }
- });
+ ResizePictureUserDecisionListener userDecisionListener = new ResizePictureUserDecisionListener(this, conversation, uri, xmppConnectionService);
+ UserDecisionDialog userDecisionDialog = new UserDecisionDialog(this, R.string.userdecision_question_resize_picture, userDecisionListener);
+ userDecisionDialog.decide(ConversationsPlusPreferences.resizePicture());
}
private void hidePrepareFileToast(final Toast prepareFileToast) {
@@ -1524,18 +1511,6 @@ public class ConversationActivity extends XmppActivity
});
}
- public boolean useSendButtonToIndicateStatus() {
- return getPreferences().getBoolean("send_button_status", false);
- }
-
- public boolean indicateReceived() {
- return getPreferences().getBoolean("indicate_received", false);
- }
-
- public boolean useWhiteBackground() {
- return getPreferences().getBoolean("use_white_background",false);
- }
-
protected boolean trustKeysIfNeeded(int requestCode) {
return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
}
@@ -1605,10 +1580,6 @@ public class ConversationActivity extends XmppActivity
xmppConnectionService.sendUnblockRequest(conversation);
}
- public boolean enterIsSend() {
- return getPreferences().getBoolean("enter_is_send",false);
- }
-
@Override
public void onShowErrorToast(final int resId) {
runOnUiThread(new Runnable() {
@@ -1618,15 +1589,4 @@ public class ConversationActivity extends XmppActivity
}
});
}
-
- public boolean highlightSelectedConversations() {
- return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard;
- }
-
- public void setMessagesLoaded() {
- if (mConversationFragment != null) {
- mConversationFragment.setMessagesLoaded();
- mConversationFragment.updateMessages();
- }
- }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 679e2a03..7a576c01 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -28,19 +29,27 @@ import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ImageButton;
+import android.widget.ImageView;
import android.widget.ListView;
+import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout;
+
import net.java.otr4j.session.SessionStatus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.UUID;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -51,9 +60,11 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.http.HttpDownloadConnection;
+import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
@@ -61,11 +72,15 @@ import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
+import eu.siacs.conversations.ui.listeners.ConversationSwipeRefreshListener;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
+import github.ankushsachdeva.emojicon.EmojiconGridView;
+import github.ankushsachdeva.emojicon.EmojiconsPopup;
+import github.ankushsachdeva.emojicon.emoji.Emojicon;
public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener {
@@ -104,113 +119,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
};
protected ListView messagesView;
+ protected SwipyRefreshLayout swipeLayout;
final protected List<Message> messageList = new ArrayList<>();
protected MessageAdapter messageListAdapter;
private EditMessage mEditMessage;
private ImageButton mSendButton;
+ private ImageView mEmojButton;
+ private View mRootView;
+ private EmojiconsPopup mEmojPopup;
private RelativeLayout snackbar;
private TextView snackbarMessage;
private TextView snackbarAction;
private boolean messagesLoaded = true;
private Toast messageLoaderToast;
-
- private OnScrollListener mOnScrollListener = new OnScrollListener() {
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- // TODO Auto-generated method stub
-
- }
-
- private int getIndexOf(String uuid, List<Message> messages) {
- if (uuid == null) {
- return messages.size() - 1;
- }
- for(int i = 0; i < messages.size(); ++i) {
- if (uuid.equals(messages.get(i).getUuid())) {
- return i;
- } else {
- Message next = messages.get(i);
- while(next != null && next.wasMergedIntoPrevious()) {
- if (uuid.equals(next.getUuid())) {
- return i;
- }
- next = next.next();
- }
-
- }
- }
- return 0;
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- synchronized (ConversationFragment.this.messageList) {
- if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) {
- long timestamp;
- if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) {
- timestamp = messageList.get(1).getTimeSent();
- } else {
- timestamp = messageList.get(0).getTimeSent();
- }
- messagesLoaded = false;
- activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() {
- @Override
- public void onMoreMessagesLoaded(final int c, Conversation conversation) {
- if (ConversationFragment.this.conversation != conversation) {
- return;
- }
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- final int oldPosition = messagesView.getFirstVisiblePosition();
- final Message message;
- if (oldPosition < messageList.size()) {
- message = messageList.get(oldPosition);
- } else {
- message = null;
- }
- String uuid = message != null ? message.getUuid() : null;
- View v = messagesView.getChildAt(0);
- final int pxOffset = (v == null) ? 0 : v.getTop();
- ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
- updateStatusMessages();
- messageListAdapter.notifyDataSetChanged();
- int pos = getIndexOf(uuid,messageList);
- messagesView.setSelectionFromTop(pos, pxOffset);
- messagesLoaded = true;
- if (messageLoaderToast != null) {
- messageLoaderToast.cancel();
- }
- }
- });
- }
-
- @Override
- public void informUser(final int resId) {
-
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (messageLoaderToast != null) {
- messageLoaderToast.cancel();
- }
- if (ConversationFragment.this.conversation != conversation) {
- return;
- }
- messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG);
- messageLoaderToast.show();
- }
- });
-
- }
- });
-
- }
- }
- }
- };
private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0;
private final int KEYCHAIN_UNLOCK_REQUIRED = 1;
private final int KEYCHAIN_UNLOCK_PENDING = 2;
@@ -300,14 +221,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE);
break;
case CANCEL:
- if (conversation != null) {
- if (conversation.getCorrectingMessage() != null) {
- conversation.setCorrectingMessage(null);
- mEditMessage.getEditableText().clear();
- }
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- conversation.setNextCounterpart(null);
- }
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setNextCounterpart(null);
updateChatMsgHint();
updateSendButton();
}
@@ -342,21 +257,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (body.length() == 0 || this.conversation == null) {
return;
}
- final Message message;
- if (conversation.getCorrectingMessage() == null) {
- message = new Message(conversation, body, conversation.getNextEncryption());
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- if (conversation.getNextCounterpart() != null) {
- message.setCounterpart(conversation.getNextCounterpart());
- message.setType(Message.TYPE_PRIVATE);
- }
+ Message message = new Message(conversation, body, conversation.getNextEncryption());
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ if (conversation.getNextCounterpart() != null) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ message.setType(Message.TYPE_PRIVATE);
}
- } else {
- message = conversation.getCorrectingMessage();
- message.setBody(body);
- message.setEdited(message.getUuid());
- message.setUuid(UUID.randomUUID().toString());
- conversation.setCorrectingMessage(null);
}
switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_OTR:
@@ -377,9 +283,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
public void updateChatMsgHint() {
final boolean multi = conversation.getMode() == Conversation.MODE_MULTI;
- if (conversation.getCorrectingMessage() != null) {
- this.mEditMessage.setHint(R.string.send_corrected_message);
- } else if (multi && conversation.getNextCounterpart() != null) {
+ if (multi && conversation.getNextCounterpart() != null) {
this.mEditMessage.setHint(getString(
R.string.send_private_message_to,
conversation.getNextCounterpart().getResourcepart()));
@@ -415,7 +319,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
public void setupIme() {
if (activity == null) {
return;
- } else if (activity.usingEnterKey() && activity.enterIsSend()) {
+ } else if (activity.usingEnterKey() && ConversationsPlusPreferences.enterIsSend()) {
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
} else if (activity.usingEnterKey()) {
@@ -443,6 +347,107 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
});
mEditMessage.setOnEditorActionListener(mEditorActionListener);
+ // Start of emojicon
+ mEmojButton = (ImageView) view.findViewById(R.id.emoji_btn);
+ mRootView = view.findViewById(R.id.textsend);
+
+ // Give the topmost view of your activity layout hierarchy. This will be used to measure soft keyboard height
+ mEmojPopup = new EmojiconsPopup(mRootView, this.getActivity());
+
+ //Will automatically set size according to the soft keyboard size
+ mEmojPopup.setSizeForSoftKeyboard();
+
+ //If the emoji popup is dismissed, change emojiButton to smiley icon
+ mEmojPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+
+ @Override
+ public void onDismiss() {
+ changeEmojiKeyboardIcon(mEmojButton, R.drawable.smiley);
+ }
+ });
+
+ //If the text keyboard closes, also dismiss the emoji popup
+ mEmojPopup.setOnSoftKeyboardOpenCloseListener(new EmojiconsPopup.OnSoftKeyboardOpenCloseListener() {
+
+ @Override
+ public void onKeyboardOpen(int keyBoardHeight) {
+
+ }
+
+ @Override
+ public void onKeyboardClose() {
+ if (mEmojPopup.isShowing())
+ mEmojPopup.dismiss();
+ }
+ });
+
+ //On emoji clicked, add it to edittext
+ mEmojPopup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() {
+
+ @Override
+ public void onEmojiconClicked(Emojicon emojicon) {
+ if (mEditMessage == null || emojicon == null) {
+ return;
+ }
+
+ int start = mEditMessage.getSelectionStart();
+ int end = mEditMessage.getSelectionEnd();
+ if (start < 0) {
+ mEditMessage.append(emojicon.getEmoji());
+ } else {
+ mEditMessage.getText().replace(Math.min(start, end),
+ Math.max(start, end), emojicon.getEmoji(), 0,
+ emojicon.getEmoji().length());
+ }
+ }
+ });
+
+ //On backspace clicked, emulate the KEYCODE_DEL key event
+ mEmojPopup.setOnEmojiconBackspaceClickedListener(new EmojiconsPopup.OnEmojiconBackspaceClickedListener() {
+
+ @Override
+ public void onEmojiconBackspaceClicked(View v) {
+ KeyEvent event = new KeyEvent(
+ 0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
+ mEditMessage.dispatchKeyEvent(event);
+ }
+ });
+
+ // To toggle between text keyboard and emoji keyboard keyboard(Popup)
+ mEmojButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ //If popup is not showing => emoji keyboard is not visible, we need to show it
+ if(!mEmojPopup.isShowing()){
+
+ //If keyboard is visible, simply show the emoji popup
+ if(mEmojPopup.isKeyBoardOpen()){
+ mEmojPopup.showAtBottom();
+ changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard);
+ }
+
+ //else, open the text keyboard first and immediately after that show the emoji popup
+ else{
+ mEditMessage.setFocusableInTouchMode(true);
+ mEditMessage.requestFocus();
+ mEmojPopup.showAtBottomPending();
+ final InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT);
+ changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard);
+ }
+ }
+
+ //If popup is showing, simply dismiss it to show the undelying text keyboard
+ else{
+ mEmojPopup.dismiss();
+ }
+ }
+ });
+
+ // End of emojicon
+
mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
mSendButton.setOnClickListener(this.mSendButtonListener);
@@ -451,7 +456,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
messagesView = (ListView) view.findViewById(R.id.messages_view);
- messagesView.setOnScrollListener(mOnScrollListener);
messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList);
messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() {
@@ -506,6 +510,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
registerForContextMenu(messagesView);
+ // Start of swipe refresh
+ // New Swipe refresh
+ swipeLayout = (SwipyRefreshLayout) view.findViewById(R.id.swipe_refresh_container);
+ swipeLayout.setOnRefreshListener(new ConversationSwipeRefreshListener(messageList, swipeLayout, this, messagesView, messageListAdapter));
+ // End of swipe refresh
+
return view;
}
@@ -522,10 +532,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private void populateContextMenu(ContextMenu menu) {
final Message m = this.selectedMessage;
final Transferable t = m.getTransferable();
- Message relevantForCorrection = m;
- while(relevantForCorrection.mergeable(relevantForCorrection.next())) {
- relevantForCorrection = relevantForCorrection.next();
- }
if (m.getType() != Message.TYPE_STATUS) {
final boolean treatAsFile = m.getType() != Message.TYPE_TEXT
&& m.getType() != Message.TYPE_PRIVATE
@@ -534,7 +540,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
menu.setHeaderTitle(R.string.message_options);
MenuItem copyText = menu.findItem(R.id.copy_text);
MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
- MenuItem correctMessage = menu.findItem(R.id.correct_message);
MenuItem shareWith = menu.findItem(R.id.share_with);
MenuItem sendAgain = menu.findItem(R.id.send_again);
MenuItem copyUrl = menu.findItem(R.id.copy_url);
@@ -549,10 +554,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
retryDecryption.setVisible(true);
}
- if (relevantForCorrection.getType() == Message.TYPE_TEXT
- && relevantForCorrection.isLastCorrectableMessage()) {
- correctMessage.setVisible(true);
- }
if (treatAsFile || (GeoHelper.isGeoUri(m.getBody()))) {
shareWith.setVisible(true);
}
@@ -566,7 +567,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
copyUrl.setVisible(true);
}
if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER)
- || (m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){
+ || (t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){
downloadFile.setVisible(true);
downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m)));
}
@@ -585,15 +586,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case R.id.msg_ctx_mnu_details:
+ new MessageDetailsDialog(getActivity(), selectedMessage).show();
+ return true;
case R.id.share_with:
shareWith(selectedMessage);
return true;
case R.id.copy_text:
copyText(selectedMessage);
return true;
- case R.id.correct_message:
- correctMessage(selectedMessage);
- return true;
case R.id.send_again:
resendMessage(selectedMessage);
return true;
@@ -625,8 +626,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
shareIntent.setType("text/plain");
} else {
shareIntent.putExtra(Intent.EXTRA_STREAM,
- activity.xmppConnectionService.getFileBackend()
- .getJingleFileUri(message));
+ FileBackend.getJingleFileUri(message));
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String mime = message.getMimeType();
if (mime == null) {
@@ -643,7 +643,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
private void copyText(Message message) {
- if (activity.copyTextToClipboard(message.getMergedBody(),
+ if (activity.copyTextToClipboard(message.getBody(),
R.string.message_text)) {
Toast.makeText(activity, R.string.message_copied_to_clipboard,
Toast.LENGTH_SHORT).show();
@@ -651,21 +651,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
private void deleteFile(Message message) {
- if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) {
+ if (FileBackend.deleteFile(message, activity.xmppConnectionService)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- activity.updateConversationList();
- updateMessages();
+ activity.xmppConnectionService.updateConversationUi();
}
}
private void resendMessage(Message message) {
if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
- DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ DownloadableFile file = FileBackend.getFile(message);
if (!file.exists()) {
Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- activity.updateConversationList();
- updateMessages();
+ activity.xmppConnectionService.updateConversationUi();
return;
}
}
@@ -682,7 +680,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
resId = R.string.file_url;
url = message.getFileParams().url.toString();
} else {
- url = message.getBody().trim();
+ url = message.getBody();
resId = R.string.file_url;
}
if (activity.copyTextToClipboard(url, resId)) {
@@ -707,8 +705,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private void retryDecryption(Message message) {
message.setEncryption(Message.ENCRYPTION_PGP);
- activity.updateConversationList();
- updateMessages();
+ activity.xmppConnectionService.updateConversationUi();
conversation.getAccount().getPgpDecryptionService().add(message);
}
@@ -719,18 +716,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateSendButton();
}
- private void correctMessage(Message message) {
- while(message.mergeable(message.next())) {
- message = message.next();
- }
- this.conversation.setCorrectingMessage(message);
- this.mEditMessage.getEditableText().clear();
- this.mEditMessage.getEditableText().append(message.getBody());
-
- }
-
protected void highlightInConference(String nick) {
- String oldString = mEditMessage.getText().toString().trim();
+ String oldString = mEditMessage.getText().toString();
if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) {
mEditMessage.getText().insert(0, nick + ": ");
} else {
@@ -785,7 +772,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mEditMessage.setText("");
this.mEditMessage.append(this.conversation.getNextMessage());
this.mEditMessage.setKeyboardListener(this);
- messageListAdapter.updatePreferences();
this.messagesView.setAdapter(messageListAdapter);
updateMessages();
this.messagesLoaded = true;
@@ -793,6 +779,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (size > 0) {
messagesView.setSelection(size - 1);
}
+ swipeLayout.setRefreshing(false);
}
private OnClickListener mEnableAccountListener = new OnClickListener() {
@@ -1051,9 +1038,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString();
final boolean empty = text.length() == 0;
final boolean conference = c.getMode() == Conversation.MODE_MULTI;
- if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) {
- action = SendButtonAction.CANCEL;
- } else if (conference && !c.getAccount().httpUploadAvailable()) {
+ if (conference && !c.getAccount().httpUploadAvailable()) {
if (empty && c.getNextCounterpart() != null) {
action = SendButtonAction.CANCEL;
} else {
@@ -1064,11 +1049,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (conference && c.getNextCounterpart() != null) {
action = SendButtonAction.CANCEL;
} else {
- String setting = activity.getPreferences().getString("quick_action", "recent");
+ String setting = ConversationsPlusPreferences.quickAction();
if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) {
setting = "location";
} else if (setting.equals("recent")) {
- setting = activity.getPreferences().getString("recently_used_quick_action", "text");
+ setting = ConversationsPlusPreferences.recentlyUsedQuickAction();
}
switch (setting) {
case "photo":
@@ -1092,7 +1077,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
action = SendButtonAction.TEXT;
}
}
- if (activity.useSendButtonToIndicateStatus() && c != null
+ if (ConversationsPlusPreferences.sendButtonStatus() && c != null
&& c.getAccount().getStatus() == Account.State.ONLINE) {
if (c.getMode() == Conversation.MODE_SINGLE) {
status = c.getContact().getMostAvailableStatus();
@@ -1106,11 +1091,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mSendButton.setImageResource(getSendButtonImageResource(action, status));
}
- protected void updateStatusMessages() {
+ public void updateStatusMessages() {
synchronized (this.messageList) {
- if (showLoadMoreMessages(conversation)) {
- this.messageList.add(0, Message.createLoadMoreMessage(conversation));
- }
if (conversation.getMode() == Conversation.MODE_SINGLE) {
ChatState state = conversation.getIncomingChatState();
if (state == ChatState.COMPOSING) {
@@ -1134,21 +1116,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
}
- private boolean showLoadMoreMessages(final Conversation c) {
- final boolean mam = hasMamSupport(c);
- final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService();
- return mam && (c.getLastClearHistory() != 0 || (c.countMessages() == 0 && c.hasMessagesLeftOnServer() && !service.queryInProgress(c)));
- }
-
- private boolean hasMamSupport(final Conversation c) {
- if (c.getMode() == Conversation.MODE_SINGLE) {
- final XmppConnection connection = c.getAccount().getXmppConnection();
- return connection != null && connection.getFeatures().mam();
- } else {
- return c.getMucOptions().mamSupport();
- }
- }
-
protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) {
snackbar.setVisibility(View.VISIBLE);
snackbar.setOnClickListener(null);
@@ -1309,7 +1276,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public boolean onEnterPressed() {
- if (activity.enterIsSend()) {
+ if (ConversationsPlusPreferences.enterIsSend()) {
sendMessage();
return true;
} else {
@@ -1344,13 +1311,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateSendButton();
}
- @Override
- public void onTextChanged() {
- if (conversation != null && conversation.getCorrectingMessage() != null) {
- updateSendButton();
- }
- }
-
private int completionIndex = 0;
private int lastCompletionLength = 0;
private String incomplete;
@@ -1417,4 +1377,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
}
+ private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){
+ iconToBeChanged.setImageResource(drawableResourceId);
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index ed80a509..720a7454 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -38,13 +38,15 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
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.services.XmppConnectionService.OnCaptchaRequested;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
+import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.UIHelper;
@@ -239,7 +241,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
if (!mFetchingAvatar) {
mFetchingAvatar = true;
- xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
+ AvatarService.getInstance().checkForAvatar(mAccount, mAvatarFetchCallback);
}
} else {
updateSaveButton();
@@ -526,9 +528,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
}
SharedPreferences preferences = getPreferences();
- boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
- this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false);
- mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example);
+ this.mShowOptions = preferences.getBoolean("show_connection_options", false);
this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
@@ -630,7 +630,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (!mInitMode) {
this.mAvatar.setVisibility(View.VISIBLE);
- this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
+ this.mAvatar.setImageBitmap(AvatarService.getInstance().get(this.mAccount, getPixel(72)));
}
if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
this.mRegisterNew.setVisibility(View.VISIBLE);
@@ -641,6 +641,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mRegisterNew.setChecked(false);
}
if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
+ this.findViewById(R.id.editAccountBoxes).setVisibility(View.GONE);
+ this.findViewById(R.id.displayAccountFrame).setVisibility(View.VISIBLE);
+ TextView detailsAccountJid = (TextView)this.findViewById(R.id.detailsAccountJid);
+ if (this.mAccount.countPresences() > 0) {
+ detailsAccountJid.setText(this.mAccount.getJid().toBareJid().toString() + " (" + this.mAccount.countPresences() + ")");
+ detailsAccountJid.setOnClickListener(new ShowResourcesListDialogListener(EditAccountActivity.this, this.mAccount.getRoster().getContact(this.mAccount.getJid().toBareJid())));
+ } else {
+ detailsAccountJid.setText(this.mAccount.getJid().toBareJid().toString());
+ }
Features features = this.mAccount.getXmppConnection().getFeatures();
this.mStats.setVisibility(View.VISIBLE);
boolean showOptimizingWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
index e3841d1d..06868a98 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
@@ -4,11 +4,11 @@ import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
-import android.widget.EditText;
import eu.siacs.conversations.Config;
+import github.ankushsachdeva.emojicon.EmojiconEditText;
-public class EditMessage extends EditText {
+public class EditMessage extends EmojiconEditText {
public EditMessage(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -69,7 +69,6 @@ public class EditMessage extends EditText {
this.isUserTyping = false;
this.keyboardListener.onTextDeleted();
}
- this.keyboardListener.onTextChanged();
}
}
@@ -85,7 +84,6 @@ public class EditMessage extends EditText {
void onTypingStarted();
void onTypingStopped();
void onTextDeleted();
- void onTextChanged();
boolean onTabPressed(boolean repeated);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
index feac2c62..c83a0275 100644
--- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -21,6 +21,8 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
+import org.openintents.openpgp.util.OpenPgpApi;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -34,8 +36,6 @@ import eu.siacs.conversations.ui.adapter.AccountAdapter;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
-import org.openintents.openpgp.util.OpenPgpApi;
-
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated {
private final String STATE_SELECTED_ACCOUNT = "selected_account";
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index 88645c4a..57fcf4e3 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -4,6 +4,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
@@ -19,10 +20,14 @@ import android.widget.Toast;
import com.soundcloud.android.crop.Crop;
import java.io.File;
+import java.io.FileNotFoundException;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.AvatarService;
+import eu.siacs.conversations.utils.ExifHelper;
import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xmpp.pep.Avatar;
@@ -110,7 +115,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (avatarUri != null) {
publishButton.setText(R.string.publishing);
disablePublishButton();
- xmppConnectionService.publishAvatar(account, avatarUri,
+ AvatarService.getInstance().publishAvatar(account, avatarUri,
avatarPublication);
}
}
@@ -190,7 +195,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
switch (requestCode) {
case REQUEST_CHOOSE_FILE_AND_CROP:
Uri source = data.getData();
- String original = FileUtils.getPath(this, source);
+ String original = FileUtils.getPath(source);
if (original != null) {
source = Uri.parse("file://"+original);
}
@@ -231,7 +236,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (this.avatarUri == null) {
if (this.account.getAvatar() != null
|| this.defaultUri == null) {
- this.avatar.setImageBitmap(avatarService().get(account, getPixel(192)));
+ this.avatar.setImageBitmap(AvatarService.getInstance().get(account, getPixel(192)));
if (this.defaultUri != null) {
this.avatar
.setOnLongClickListener(this.backToDefaultListener);
@@ -276,7 +281,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
protected void loadImageIntoPreview(Uri uri) {
Bitmap bm = null;
try {
- bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, getPixel(192));
+ bm = ImageUtil.cropCenterSquare(uri, getPixel(192));
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 750a7421..5b1978c4 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -9,6 +9,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
+import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
@@ -25,11 +26,14 @@ import java.util.List;
import java.util.Locale;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.ExportLogsService;
import eu.siacs.conversations.xmpp.XmppConnection;
+import github.ankushsachdeva.emojicon.EmojiconHandler;
public class SettingsActivity extends XmppActivity implements
OnSharedPreferenceChangeListener {
@@ -67,14 +71,6 @@ public class SettingsActivity extends XmppActivity implements
}
}
- if (Config.FORCE_ORBOT) {
- PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options");
- PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert");
- if (connectionOptions != null) {
- expert.removePreference(connectionOptions);
- }
- }
-
final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@@ -106,45 +102,42 @@ public class SettingsActivity extends XmppActivity implements
}
});
- dialogBuilder.setPositiveButton(
- getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- int count = selectedItems.size();
- if (count > 0) {
- for (int i = 0; i < count; i++) {
- try {
- Integer item = Integer.valueOf(selectedItems.get(i).toString());
- String alias = aliases.get(item);
- mtm.deleteCertificate(alias);
- } catch (KeyStoreException e) {
- e.printStackTrace();
- displayToast("Error: " + e.getLocalizedMessage());
- }
- }
- if (xmppConnectionServiceBound) {
- reconnectAccounts();
- }
- displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
- }
- }
- });
- dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
- AlertDialog removeCertsDialog = dialogBuilder.create();
- removeCertsDialog.show();
- removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
- return true;
- }
- });
-
- final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
- exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- hasStoragePermission(REQUEST_WRITE_LOGS);
- return true;
- }
- });
+ dialogBuilder.setPositiveButton(
+ getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int count = selectedItems.size();
+ if (count > 0) {
+ for (int i = 0; i < count; i++) {
+ try {
+ Integer item = Integer.valueOf(selectedItems.get(i).toString());
+ String alias = aliases.get(item);
+ mtm.deleteCertificate(alias);
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ displayToast("Error: " + e.getLocalizedMessage());
+ }
+ }
+ if (xmppConnectionServiceBound) {
+ reconnectAccounts();
+ }
+ displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
+ }
+ }
+ });
+ dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
+ AlertDialog removeCertsDialog = dialogBuilder.create();
+ removeCertsDialog.show();
+ removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ return true;
+ }
+ });
+ // Avoid appearence of setting to enable or disable omemo in screen
+ Preference omemoEnabledPreference = this.mSettingsFragment.findPreference("omemo_enabled");
+ PreferenceCategory otherExpertSettingsGroup = (PreferenceCategory) this.mSettingsFragment.findPreference("other_expert_settings");
+ if (null != omemoEnabledPreference && null != otherExpertSettingsGroup) {
+ otherExpertSettingsGroup.removePreference(omemoEnabledPreference);
+ }
}
@Override
@@ -157,11 +150,12 @@ public class SettingsActivity extends XmppActivity implements
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String name) {
final List<String> resendPresence = Arrays.asList(
- "confirm_messages",
+ "confirm_messages_list",
"xa_on_silent_mode",
"away_when_screen_off",
- "allow_message_correction",
"treat_vibrate_as_silent");
+ // need to synchronize the settings class first
+ Settings.synchronizeSettingsClassWithPreferences(preferences, name);
if (name.equals("resource")) {
String resource = preferences.getString("resource", "mobile")
.toLowerCase(Locale.US);
@@ -190,8 +184,10 @@ public class SettingsActivity extends XmppActivity implements
} else if (name.equals("dont_trust_system_cas")) {
xmppConnectionService.updateMemorizingTrustmanager();
reconnectAccounts();
- } else if (name.equals("use_tor")) {
- reconnectAccounts();
+ } else if ("parse_emoticons".equals(name)) {
+ EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS);
+ } else if ("file_transfer_folder".equals(name)) {
+ FileBackend.createNoMedia();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
index ae722081..91dd32d9 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -19,6 +19,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog;
+import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener;
+import de.thedevstack.conversationsplus.ui.listeners.ShareWithResizePictureUserDecisionListener;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -270,26 +274,31 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
return;
}
if (share.uris.size() != 0) {
- OnPresenceSelected callback = new OnPresenceSelected() {
- @Override
- public void onPresenceSelected() {
- attachmentCounter.set(share.uris.size());
- if (share.image) {
- share.multiple = share.uris.size() > 1;
- replaceToast(getString(share.multiple ? R.string.preparing_images : R.string.preparing_image));
- for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
- ShareWithActivity.this.xmppConnectionService
- .attachImageToConversation(conversation, i.next(),
- attachFileCallback);
- }
- } else {
+ OnPresenceSelected callback;
+ if (this.share.image) {
+ // TODO: attachementCounter should be set and decremented correctly
+ callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ ResizePictureUserDecisionListener userDecisionListener = new ShareWithResizePictureUserDecisionListener(ShareWithActivity.this, conversation, xmppConnectionService, share.uris);
+ UserDecisionDialog userDecisionDialog = new UserDecisionDialog(ShareWithActivity.this, R.string.userdecision_question_resize_picture, userDecisionListener);
+ userDecisionDialog.decide(ConversationsPlusPreferences.resizePicture());
+ }
+ };
+ } else {
+ attachmentCounter.set(share.uris.size());
+ callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
replaceToast(getString(R.string.preparing_file));
ShareWithActivity.this.xmppConnectionService
.attachFileToConversation(conversation, share.uris.get(0),
attachFileCallback);
+ switchToConversation(conversation, null, true);
+ finish();
}
- }
- };
+ };
+ }
if (conversation.getAccount().httpUploadAvailable()) {
callback.onPresenceSelected();
} else {
@@ -297,6 +306,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
}
} else {
switchToConversation(conversation, this.share.text, true);
+ finish();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 7d650e5b..a6468970 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -26,7 +26,6 @@ import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.Editable;
import android.text.TextWatcher;
-import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
@@ -56,6 +55,8 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -192,6 +193,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_conversation);
+ this.mHideOfflineContacts = ConversationsPlusPreferences.hideOffline();
mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
@@ -248,7 +250,6 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
}
});
- this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false);
}
@@ -549,12 +550,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
new IntentIntegrator(this).initiateScan();
return true;
case R.id.action_hide_offline:
- mHideOfflineContacts = !item.isChecked();
- getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
+ mHideOfflineContacts = !item.isChecked(); // the item is the menu item which is displayed, the inversion here calculates the new value
+ ConversationsPlusPreferences.commitHideOffline(mHideOfflineContacts);
if (mSearchEditText != null) {
filter(mSearchEditText.getText().toString());
}
- invalidateOptionsMenu();
+ invalidateOptionsMenu(); // Since the selection of this item changed the checked value, the options menu is now invalid
+ return true;
}
return super.onOptionsItemSelected(item);
}
@@ -668,12 +670,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
switch (intent.getAction()) {
case Intent.ACTION_SENDTO:
case Intent.ACTION_VIEW:
- Log.d(Config.LOGTAG, "received uri=" + intent.getData());
+ Logging.d(Config.LOGTAG, "received uri=" + intent.getData());
return new Invite(intent.getData()).invite();
case NfcAdapter.ACTION_NDEF_DISCOVERED:
for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
if (message instanceof NdefMessage) {
- Log.d(Config.LOGTAG, "received message=" + message);
+ Logging.d(Config.LOGTAG, "received message=" + message);
for (NdefRecord record : ((NdefMessage) message).getRecords()) {
switch (record.getTnf()) {
case NdefRecord.TNF_WELL_KNOWN:
@@ -705,7 +707,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
Contact contact = contacts.get(0);
if (invite.getFingerprint() != null) {
if (contact.addOtrFingerprint(invite.getFingerprint())) {
- Log.d(Config.LOGTAG,"added new fingerprint");
+ Logging.d(Config.LOGTAG,"added new fingerprint");
xmppConnectionService.syncRosterToDisk(contact.getAccount());
}
}
@@ -740,7 +742,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
Presence.Status s = p == null ? Presence.Status.OFFLINE : p.getStatus();
if (contact.showInRoster() && contact.match(needle)
&& (!this.mHideOfflineContacts
- || (needle != null && !needle.trim().isEmpty())
+ || (needle != null && !needle.isEmpty())
|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
this.contacts.add(contact);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index fe3a3031..2e2700e9 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -41,7 +41,6 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -50,6 +49,7 @@ import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
@@ -69,6 +69,10 @@ import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
@@ -78,10 +82,8 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
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.OnKeyStatusUpdated;
@@ -359,18 +361,18 @@ public abstract class XmppActivity extends Activity {
super.onCreate(savedInstanceState);
metrics = getResources().getDisplayMetrics();
ExceptionHelper.init(getApplicationContext());
- mPrimaryTextColor = getResources().getColor(R.color.black87);
- mSecondaryTextColor = getResources().getColor(R.color.black54);
+ mPrimaryTextColor = getResources().getColor(R.color.primaryText);
+ mSecondaryTextColor = getResources().getColor(R.color.secondaryText);
mTertiaryTextColor = getResources().getColor(R.color.black12);
- mColorRed = getResources().getColor(R.color.red800);
+ mColorRed = getResources().getColor(R.color.warning);
mColorOrange = getResources().getColor(R.color.orange500);
- mColorGreen = getResources().getColor(R.color.green500);
+ mColorGreen = getResources().getColor(R.color.online);
mPrimaryColor = getResources().getColor(R.color.primary);
- mPrimaryBackgroundColor = getResources().getColor(R.color.grey50);
- mSecondaryBackgroundColor = getResources().getColor(R.color.grey200);
+ mPrimaryBackgroundColor = getResources().getColor(R.color.primaryBackground);
+ mSecondaryBackgroundColor = getResources().getColor(R.color.secondaryBackground);
this.mTheme = findTheme();
setTheme(this.mTheme);
- this.mUsingEnterKey = usingEnterKey();
+ this.mUsingEnterKey = ConversationsPlusPreferences.displayEnterKey();
mUseSubject = getPreferences().getBoolean("use_subject", true);
final ActionBar ab = getActionBar();
if (ab!=null) {
@@ -558,7 +560,7 @@ public abstract class XmppActivity extends Activity {
});
}
- protected void displayErrorDialog(final int errorCode) {
+ public void displayErrorDialog(final int errorCode) {
runOnUiThread(new Runnable() {
@Override
@@ -743,7 +745,7 @@ public abstract class XmppActivity extends Activity {
case UNTRUSTED:
case TRUSTED:
case TRUSTED_X509:
- trustToggle.setChecked(trust.trusted(), false);
+ trustToggle.setChecked(trust.trusted());
trustToggle.setEnabled(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509);
if (Config.X509_VERIFICATION && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
trustToggle.setOnClickListener(null);
@@ -752,7 +754,7 @@ public abstract class XmppActivity extends Activity {
keyType.setTextColor(getSecondaryTextColor());
break;
case UNDECIDED:
- trustToggle.setChecked(false, false);
+ trustToggle.setChecked(false);
trustToggle.setEnabled(false);
key.setTextColor(getPrimaryTextColor());
keyType.setTextColor(getSecondaryTextColor());
@@ -760,7 +762,7 @@ public abstract class XmppActivity extends Activity {
case INACTIVE_UNTRUSTED:
case INACTIVE_UNDECIDED:
trustToggle.setOnClickListener(null);
- trustToggle.setChecked(false, false);
+ trustToggle.setChecked(false);
trustToggle.setEnabled(false);
key.setTextColor(getTertiaryTextColor());
keyType.setTextColor(getTertiaryTextColor());
@@ -768,7 +770,7 @@ public abstract class XmppActivity extends Activity {
case INACTIVE_TRUSTED:
case INACTIVE_TRUSTED_X509:
trustToggle.setOnClickListener(null);
- trustToggle.setChecked(true, false);
+ trustToggle.setChecked(true);
trustToggle.setEnabled(false);
key.setTextColor(getTertiaryTextColor());
keyType.setTextColor(getTertiaryTextColor());
@@ -1022,7 +1024,7 @@ public abstract class XmppActivity extends Activity {
}
protected int findTheme() {
- if (getPreferences().getBoolean("use_larger_font", false)) {
+ if (ConversationsPlusPreferences.useLargerFont()) {
return R.style.ConversationsTheme_LargerText;
} else {
return R.style.ConversationsTheme;
@@ -1051,7 +1053,7 @@ public abstract class XmppActivity extends Activity {
}
protected Bitmap createQrCodeBitmap(String input, int size) {
- Log.d(Config.LOGTAG,"qr code requested size: "+size);
+ Logging.d(Config.LOGTAG,"qr code requested size: "+size);
try {
final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
@@ -1067,7 +1069,7 @@ public abstract class XmppActivity extends Activity {
}
}
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
+ Logging.d(Config.LOGTAG,"output size: "+width+"x"+height);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
} catch (final WriterException e) {
@@ -1126,24 +1128,21 @@ public abstract class XmppActivity extends Activity {
}
}
- public AvatarService avatarService() {
- return xmppConnectionService.getAvatarService();
- }
-
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
- private Message message = null;
+ private final boolean setSize;
+ private Message message = null;
- public BitmapWorkerTask(ImageView imageView) {
+ public BitmapWorkerTask(ImageView imageView, boolean setSize) {
imageViewReference = new WeakReference<>(imageView);
+ this.setSize = setSize;
}
@Override
protected Bitmap doInBackground(Message... params) {
message = params[0];
try {
- return xmppConnectionService.getFileBackend().getThumbnail(
- message, (int) (metrics.density * 288), false);
+ return ImageUtil.getThumbnail(message, (int) (metrics.density * 288), false);
} catch (FileNotFoundException e) {
return null;
}
@@ -1156,27 +1155,35 @@ public abstract class XmppActivity extends Activity {
if (imageView != null) {
imageView.setImageBitmap(bitmap);
imageView.setBackgroundColor(0x00000000);
+ if (setSize) {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(
+ bitmap.getWidth(), bitmap.getHeight()));
+ }
}
}
}
}
- public void loadBitmap(Message message, ImageView imageView) {
+ public void loadBitmap(Message message, ImageView imageView, boolean setSize) {
Bitmap bm;
try {
- bm = xmppConnectionService.getFileBackend().getThumbnail(message,
- (int) (metrics.density * 288), true);
+ bm = ImageUtil.getThumbnail(message,(int) (metrics.density * 288), true);
} catch (FileNotFoundException e) {
bm = null;
}
+
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
+ if (setSize) {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(
+ bm.getWidth(), bm.getHeight()));
+ }
} else {
if (cancelPotentialWork(message, imageView)) {
imageView.setBackgroundColor(0xff333333);
imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView, setSize);
final AsyncDrawable asyncDrawable = new AsyncDrawable(
getResources(), null, task);
imageView.setImageDrawable(asyncDrawable);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
index 98250af9..b8f15e0e 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.ImageView;
+import android.widget.Switch;
import android.widget.TextView;
import java.util.List;
@@ -14,9 +15,9 @@ import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.AvatarService;
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> {
@@ -43,7 +44,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
}
TextView statusView = (TextView) view.findViewById(R.id.account_status);
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
- imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
+ imageView.setImageBitmap(AvatarService.getInstance().get(account, activity.getPixel(48)));
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
@@ -59,12 +60,13 @@ public class AccountAdapter extends ArrayAdapter<Account> {
}
final Switch tglAccountState = (Switch) view.findViewById(R.id.tgl_account_status);
final boolean isDisabled = (account.getStatus() == Account.State.DISABLED);
- tglAccountState.setChecked(!isDisabled,false);
+ tglAccountState.setChecked(!isDisabled);
tglAccountState.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- if (b == isDisabled && activity instanceof ManageAccountActivity) {
- ((ManageAccountActivity) activity).onClickTglAccountState(account,b);
+ // Condition compoundButton.isPressed() added because of http://stackoverflow.com/a/28219410
+ if (compoundButton.isPressed() && b == isDisabled && activity instanceof ManageAccountActivity) {
+ ((ManageAccountActivity) activity).onClickTglAccountState(account, b);
}
}
});
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
index f5f48a26..2e40717a 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -3,6 +3,7 @@ package eu.siacs.conversations.ui.adapter;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -19,10 +20,15 @@ import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.utils.UIHelper;
@@ -44,11 +50,12 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
view = inflater.inflate(R.layout.conversation_list_row,parent, false);
}
Conversation conversation = getItem(position);
+ // Highlight the currently selected conversation
if (this.activity instanceof ConversationActivity) {
- View swipeableItem = view.findViewById(R.id.swipeable_item);
ConversationActivity a = (ConversationActivity) this.activity;
- int c = a.highlightSelectedConversations() && conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
- swipeableItem.setBackgroundColor(c);
+ int c = conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
+ view.findViewById(R.id.conversationListRowContent).setBackgroundColor(c);
+ view.findViewById(R.id.conversationListRowFrame).setBackgroundColor(c);
}
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
@@ -61,6 +68,30 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage);
ImageView notificationStatus = (ImageView) view.findViewById(R.id.notification_status);
+ if (Settings.SHOW_ONLINE_STATUS && conversation.getAccount().getStatus() == Account.State.ONLINE) {
+ TextView status = (TextView) view.findViewById(R.id.status);
+
+ String color = "#000000";
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ switch (conversation.getContact().getMostAvailableStatus()) {
+ case ONLINE:
+ case CHAT:
+ color = "#259B23";
+ break;
+ case AWAY:
+ case XA:
+ color = "#FF9800";
+ break;
+ case DND:
+ color = "#E51C23";
+ break;
+ }
+ } else if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().online()) {
+ color = "#259B23";
+ }
+ status.setBackgroundColor(Color.parseColor(color));
+ }
+
Message message = conversation.getLatestMessage();
if (!conversation.isRead()) {
@@ -74,12 +105,23 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|| message.getTransferable().getStatus() != Transferable.STATUS_DELETED)) {
mLastMessage.setVisibility(View.GONE);
imagePreview.setVisibility(View.VISIBLE);
- activity.loadBitmap(message, imagePreview);
+ activity.loadBitmap(message, imagePreview, false);
} else {
Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message);
mLastMessage.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
- mLastMessage.setText(preview.first);
+ CharSequence msgText = preview.first;
+ String msgPrefix = null;
+ if (message.getStatus() == Message.STATUS_SEND
+ || message.getStatus() == Message.STATUS_SEND_DISPLAYED
+ || message.getStatus() == Message.STATUS_SEND_FAILED
+ || message.getStatus() == Message.STATUS_SEND_RECEIVED) {
+ msgPrefix = activity.getString(R.string.cplus_me);
+ } else if (conversation.getMode() == Conversation.MODE_MULTI) {
+ msgPrefix = UIHelper.getMessageDisplayName(message);
+ }
+ String lastMessagePreview = ((null == msgPrefix || msgPrefix.isEmpty()) ? "" : (msgPrefix + ": ")) + msgText;
+ mLastMessage.setText(lastMessagePreview);
if (preview.second) {
if (conversation.isRead()) {
mLastMessage.setTypeface(null, Typeface.ITALIC);
@@ -109,9 +151,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
notificationStatus.setImageResource(R.drawable.ic_notifications_none_grey600_24dp);
}
- mTimestamp.setText(UIHelper.readableTimeDifference(activity,conversation.getLatestMessage().getTimeSent()));
+ mTimestamp.setText(UIHelper.readableTimeDifference(activity, message.getTimeSent()));
ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image);
- loadAvatar(conversation,profilePicture);
+ profilePicture.setOnLongClickListener(new ShowResourcesListDialogListener(activity, conversation.getContact()));
+ loadAvatar(conversation, profilePicture);
return view;
}
@@ -126,7 +169,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
@Override
protected Bitmap doInBackground(Conversation... params) {
- return activity.avatarService().get(params[0], activity.getPixel(56));
+ return AvatarService.getInstance().get(params[0], activity.getPixel(56));
}
@Override
@@ -143,7 +186,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
public void loadAvatar(Conversation conversation, ImageView imageView) {
if (cancelPotentialWork(conversation, imageView)) {
- final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
+ final Bitmap bm = AvatarService.getInstance().get(conversation, activity.getPixel(56), true);
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index da8e3910..29d706c7 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -1,13 +1,11 @@
package eu.siacs.conversations.ui.adapter;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -20,8 +18,11 @@ import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.ListItem;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -45,8 +46,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
public ListItemAdapter(XmppActivity activity, List<ListItem> objects) {
super(activity, 0, objects);
this.activity = activity;
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
- this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
+ this.showDynamicTags = ConversationsPlusPreferences.showDynamicTags();
}
@Override
@@ -57,6 +57,12 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
if (view == null) {
view = inflater.inflate(R.layout.contact, parent, false);
}
+
+ if (Settings.SHOW_ONLINE_STATUS) {
+ TextView tvStatus = (TextView) view.findViewById(R.id.contact_status);
+ tvStatus.setBackgroundColor(item.getStatusColor());
+ }
+
TextView tvName = (TextView) view.findViewById(R.id.contact_display_name);
TextView tvJid = (TextView) view.findViewById(R.id.contact_jid);
ImageView picture = (ImageView) view.findViewById(R.id.contact_photo);
@@ -106,7 +112,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
@Override
protected Bitmap doInBackground(ListItem... params) {
- return activity.avatarService().get(params[0], activity.getPixel(48));
+ return AvatarService.getInstance().get(params[0], activity.getPixel(48));
}
@Override
@@ -123,7 +129,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
public void loadAvatar(ListItem item, ImageView imageView) {
if (cancelPotentialWork(item, imageView)) {
- final Bitmap bm = activity.avatarService().get(item,activity.getPixel(48),true);
+ final Bitmap bm = AvatarService.getInstance().get(item,activity.getPixel(48),true);
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
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 ab9f74a3..b5d1272c 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -38,6 +38,7 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
@@ -46,6 +47,8 @@ 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.services.AvatarService;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.GeoHelper;
@@ -56,6 +59,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
private static final int SENT = 0;
private static final int RECEIVED = 1;
private static final int STATUS = 2;
+ private static final int NULL = 3;
private static final Pattern XMPP_PATTERN = Pattern
.compile("xmpp\\:(?:(?:["
+ Patterns.GOOD_IRI_CHAR
@@ -77,14 +81,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return true;
}
};
- private boolean mIndicateReceived = false;
- private boolean mUseWhiteBackground = false;
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
super(activity, 0, messages);
this.activity = activity;
metrics = getContext().getResources().getDisplayMetrics();
- updatePreferences();
}
public void setOnContactPictureClicked(OnContactPictureClicked listener) {
@@ -132,17 +133,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.indicatorReceived.setVisibility(View.GONE);
}
- if (viewHolder.edit_indicator != null) {
- if (message.edited()) {
- viewHolder.edit_indicator.setVisibility(View.VISIBLE);
- viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp);
- viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f);
- } else {
- viewHolder.edit_indicator.setVisibility(View.GONE);
- }
- }
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
- && message.getMergedStatus() <= Message.STATUS_RECEIVED;
+ && message.getStatus() <= Message.STATUS_RECEIVED;
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
FileParams params = message.getFileParams();
if (params.size > (1.5 * 1024 * 1024)) {
@@ -154,7 +146,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
error = true;
}
}
- switch (message.getMergedStatus()) {
+ switch (message.getStatus()) {
case Message.STATUS_WAITING:
info = getContext().getString(R.string.waiting);
break;
@@ -170,12 +162,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
info = getContext().getString(R.string.offering);
break;
case Message.STATUS_SEND_RECEIVED:
- if (mIndicateReceived) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
case Message.STATUS_SEND_DISPLAYED:
- if (mIndicateReceived) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
@@ -226,7 +218,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
- message.getMergedTimeSent());
+ message.getTimeSent());
if (message.getStatus() <= Message.STATUS_RECEIVED) {
if ((filesize != null) && (info != null)) {
viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info);
@@ -279,19 +271,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
- if (viewHolder.download_button != null) {
- viewHolder.download_button.setVisibility(View.GONE);
- }
- viewHolder.image.setVisibility(View.GONE);
- viewHolder.messageBody.setVisibility(View.VISIBLE);
- viewHolder.messageBody.setIncludeFontPadding(false);
- Spannable span = new SpannableString(body);
- span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- viewHolder.messageBody.setText(span);
- }
-
private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
@@ -302,10 +281,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (message.getBody() != null) {
final String nick = UIHelper.getMessageDisplayName(message);
String body;
- try {
- body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " ");
- } catch (ArrayIndexOutOfBoundsException e) {
- body = message.getMergedBody();
+ if (message.hasMeCommand()) {
+ body = message.getBodyReplacedMeCommand(nick);
+ } else {
+ body = message.getBody();
}
final SpannableString formattedBody = new SpannableString(body);
int i = body.indexOf(Message.MERGE_SEPARATOR);
@@ -351,26 +330,54 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
viewHolder.messageBody.setText(span);
}
- int urlCount = 0;
- Matcher matcher = Patterns.WEB_URL.matcher(body);
- while (matcher.find()) {
- urlCount++;
+ int patternMatchCount = 0;
+ int oldAutoLinkMask = viewHolder.messageBody.getAutoLinkMask();
+
+ // first check if we have a match on XMPP_PATTERN so we do not have to check for EMAIL_ADDRESSES
+ patternMatchCount += countMatches(XMPP_PATTERN, body);
+ if ((Linkify.EMAIL_ADDRESSES & oldAutoLinkMask) != 0 && patternMatchCount > 0) {
+ oldAutoLinkMask -= Linkify.EMAIL_ADDRESSES;
+ }
+
+ // count matches for all patterns
+ if ((Linkify.WEB_URLS & oldAutoLinkMask) != 0) {
+ patternMatchCount += countMatches(Patterns.WEB_URL, body);
}
- viewHolder.messageBody.setTextIsSelectable(urlCount <= 1);
+ if ((Linkify.EMAIL_ADDRESSES & oldAutoLinkMask) != 0) {
+ patternMatchCount += countMatches(Patterns.EMAIL_ADDRESS, body);
+ }
+ if ((Linkify.PHONE_NUMBERS & oldAutoLinkMask) != 0) {
+ patternMatchCount += countMatches(Patterns.PHONE, body);
+ }
+
+ viewHolder.messageBody.setTextIsSelectable(patternMatchCount <= 1);
viewHolder.messageBody.setAutoLinkMask(0);
- Linkify.addLinks(viewHolder.messageBody, Linkify.WEB_URLS);
Linkify.addLinks(viewHolder.messageBody, XMPP_PATTERN, "xmpp");
+ viewHolder.messageBody.setAutoLinkMask(oldAutoLinkMask);
} else {
viewHolder.messageBody.setText("");
viewHolder.messageBody.setTextIsSelectable(false);
}
viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true));
- viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true));
- viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
viewHolder.messageBody.setOnLongClickListener(openContextMenu);
}
+ /**
+ * Counts the number of occurrences of the pattern in body.
+ * @param pattern the pattern to match
+ * @param body the body to find the pattern
+ * @return the number of occurrences
+ */
+ private int countMatches(Pattern pattern, String body) {
+ Matcher matcher = pattern.matcher(body);
+ int count = 0;
+ while (matcher.find()) {
+ count++;
+ }
+ return count;
+ }
+
private void displayDownloadableMessage(ViewHolder viewHolder,
final Message message, String text) {
viewHolder.image.setVisibility(View.GONE);
@@ -425,7 +432,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE);
FileParams params = message.getFileParams();
- double target = metrics.density * 288;
+ //TODO: Check what value add the following lines have (compared with setting height/width in XmppActivity.loadBitmap from thumbnail after thumbnail is created)
+ /*double target = metrics.density * 288;
int scalledW;
int scalledH;
if (params.width <= params.height) {
@@ -437,8 +445,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH);
layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
- viewHolder.image.setLayoutParams(layoutParams);
- activity.loadBitmap(message, viewHolder.image);
+ viewHolder.image.setLayoutParams(layoutParams);*/
+ //TODO Why should this be calculated by hand???
+ activity.loadBitmap(message, viewHolder.image, true);
viewHolder.image.setOnClickListener(new OnClickListener() {
@Override
@@ -449,19 +458,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.image.setOnLongClickListener(openContextMenu);
}
- private void loadMoreMessages(Conversation conversation) {
- conversation.setLastClearHistory(0);
- conversation.setHasMessagesLeftOnServer(true);
- conversation.setFirstMamReference(null);
- long timestamp = conversation.getLastMessageTransmitted();
- if (timestamp == 0) {
- timestamp = System.currentTimeMillis();
- }
- activity.setMessagesLoaded();
- activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp);
- Toast.makeText(activity, R.string.fetching_history_from_server,Toast.LENGTH_LONG).show();
- }
-
@Override
public View getView(int position, View view, ViewGroup parent) {
final Message message = getItem(position);
@@ -484,7 +480,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
.findViewById(R.id.download_button);
viewHolder.indicator = (ImageView) view
.findViewById(R.id.security_indicator);
- viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
viewHolder.image = (ImageView) view
.findViewById(R.id.message_image);
viewHolder.messageBody = (TextView) view
@@ -505,7 +500,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
.findViewById(R.id.download_button);
viewHolder.indicator = (ImageView) view
.findViewById(R.id.security_indicator);
- viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
viewHolder.image = (ImageView) view
.findViewById(R.id.message_image);
viewHolder.messageBody = (TextView) view
@@ -520,7 +514,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
- viewHolder.load_more_messages = (Button) view.findViewById(R.id.load_more_messages);
break;
default:
viewHolder = null;
@@ -534,31 +527,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- boolean darkBackground = (type == RECEIVED && (!isInValidSession || !mUseWhiteBackground));
+ boolean darkBackground = (type == RECEIVED && !isInValidSession);
if (type == STATUS) {
- if ("LOAD_MORE".equals(message.getBody())) {
- viewHolder.status_message.setVisibility(View.GONE);
- viewHolder.contact_picture.setVisibility(View.GONE);
- viewHolder.load_more_messages.setVisibility(View.VISIBLE);
- viewHolder.load_more_messages.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- loadMoreMessages(message.getConversation());
- }
- });
- } else {
- viewHolder.status_message.setVisibility(View.VISIBLE);
- viewHolder.contact_picture.setVisibility(View.VISIBLE);
- viewHolder.load_more_messages.setVisibility(View.GONE);
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- viewHolder.contact_picture.setImageBitmap(activity
- .avatarService().get(conversation.getContact(),
- activity.getPixel(32)));
- viewHolder.contact_picture.setAlpha(0.5f);
- }
- viewHolder.status_message.setText(message.getBody());
+ viewHolder.status_message.setVisibility(View.VISIBLE);
+ viewHolder.contact_picture.setVisibility(View.VISIBLE);
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(),
+ activity.getPixel(32)));
+ viewHolder.contact_picture.setAlpha(0.5f);
}
+ viewHolder.status_message.setText(message.getBody());
return view;
} else {
loadAvatar(message, viewHolder.contact_picture);
@@ -633,8 +612,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else {
if (GeoHelper.isGeoUri(message.getBody())) {
displayLocationMessage(viewHolder,message);
- } else if (message.bodyIsHeart()) {
- displayHeartMessage(viewHolder, message.getBody().trim());
} else if (message.treatAsDownloadable() == Message.Decision.MUST) {
try {
URL url = new URL(message.getBody());
@@ -656,11 +633,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (type == RECEIVED) {
if(isInValidSession) {
- if (mUseWhiteBackground) {
- viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_white);
- } else {
- viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received);
- }
viewHolder.encryption.setVisibility(View.GONE);
} else {
viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
@@ -675,7 +647,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
public void openDownloadable(Message message) {
- DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ DownloadableFile file = FileBackend.getFile(message);
if (!file.exists()) {
Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
return;
@@ -710,11 +682,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
}
- public void updatePreferences() {
- this.mIndicateReceived = activity.indicateReceived();
- this.mUseWhiteBackground = activity.useWhiteBackground();
- }
-
public interface OnContactPictureClicked {
void onContactPictureClicked(Message message);
}
@@ -735,8 +702,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
protected ImageView contact_picture;
protected TextView status_message;
protected TextView encryption;
- public Button load_more_messages;
- public ImageView edit_indicator;
}
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
@@ -749,7 +714,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
@Override
protected Bitmap doInBackground(Message... params) {
- return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled());
+ return AvatarService.getInstance().get(params[0], activity.getPixel(48), isCancelled());
}
@Override
@@ -766,7 +731,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
public void loadAvatar(Message message, ImageView imageView) {
if (cancelPotentialWork(message, imageView)) {
- final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true);
+ final Bitmap bm = AvatarService.getInstance().get(message, activity.getPixel(48), true);
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
index 3a21ade3..3b051191 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
@@ -4,13 +4,11 @@ import android.content.Context;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import java.util.List;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Field;
diff --git a/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java
new file mode 100644
index 00000000..08916206
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java
@@ -0,0 +1,143 @@
+package eu.siacs.conversations.ui.listeners;
+
+import android.view.View;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout;
+
+import java.util.List;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.ui.ConversationFragment;
+import eu.siacs.conversations.ui.adapter.MessageAdapter;
+
+/**
+ * This listener updates the UI when messages are loaded from the server.
+ */
+public class ConversationMoreMessagesLoadedListener implements XmppConnectionService.OnMoreMessagesLoaded {
+ private SwipyRefreshLayout swipeLayout;
+ private List<Message> messageList;
+ private ConversationFragment fragment;
+ private ListView messagesView;
+ private MessageAdapter messageListAdapter;
+ private Toast messageLoaderToast;
+ /*
+ The current loading status
+ */
+ private boolean loadingMessages = false;
+ /**
+ * Whether the user is loading only history messages or not.
+ * History messages are messages which are older than the oldest in the database.
+ */
+ private boolean loadHistory = true;
+
+ public ConversationMoreMessagesLoadedListener(SwipyRefreshLayout swipeLayout, List<Message> messageList, ConversationFragment fragment, ListView messagesView, MessageAdapter messageListAdapter) {
+ this.swipeLayout = swipeLayout;
+ this.messageList = messageList;
+ this.fragment = fragment;
+ this.messagesView = messagesView;
+ this.messageListAdapter = messageListAdapter;
+ }
+
+ public void setLoadHistory(boolean value) {
+ this.loadHistory = value;
+ }
+
+ public void setLoadingInProgress() {
+ this.loadingMessages = true;
+ }
+
+ public boolean isLoadingInProgress() {
+ return this.loadingMessages;
+ }
+
+ @Override
+ public void onMoreMessagesLoaded(final int c, final Conversation conversation) {
+ ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+ // Current selected conversation is not the same the messages are loaded - skip updating message view and hide loading graphic
+ if (activity.getSelectedConversation() != conversation) {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ swipeLayout.setRefreshing(false);
+ }
+ });
+ return;
+ }
+ // No new messages are loaded
+ if (0 == c) {
+ if (this.loadHistory) {
+ conversation.setHasMessagesLeftOnServer(false);
+ }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ swipeLayout.setRefreshing(false);
+ }
+ });
+ }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int oldPosition = messagesView.getFirstVisiblePosition(); // Always 0 - because loading starts always when hitting the top
+ String uuid = null;
+ boolean oldMessageListWasEmpty = messageList.isEmpty();
+ if (-1 < oldPosition && messageList.size() > oldPosition) {
+ Message message = messageList.get(oldPosition);
+ uuid = message != null ? message.getUuid() : null;
+ }
+ View v = messagesView.getChildAt(0);
+ final int pxOffset = (v == null) ? 0 : v.getTop();
+
+ conversation.populateWithMessages(messageList); // This overrides the old message list
+ fragment.updateStatusMessages(); // This adds "messages" to the list for the status
+ messageListAdapter.notifyDataSetChanged();
+ loadingMessages = false; // Loading of messages is finished - next query can be loaded
+
+ int pos = getIndexOf(uuid, messageList);
+
+ if (!oldMessageListWasEmpty) {
+ messagesView.setSelectionFromTop(pos, pxOffset);
+ }
+
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ swipeLayout.setRefreshing(false);
+ }
+ });
+ }
+
+ @Override
+ public void informUser(final int resId) {
+ final ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG);
+ messageLoaderToast.show();
+ }
+ });
+
+ }
+
+ private int getIndexOf(String uuid, List<Message> messages) {
+ if (uuid == null) {
+ return 0;
+ }
+ for (int i = 0; i < messages.size(); ++i) {
+ if (uuid.equals(messages.get(i).getUuid())) {
+ return i;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java
new file mode 100644
index 00000000..0cbde814
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java
@@ -0,0 +1,103 @@
+package eu.siacs.conversations.ui.listeners;
+
+import android.widget.ListView;
+
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout;
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection;
+
+import java.util.List;
+
+import de.thedevstack.android.logcat.Logging;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.MessageArchiveService;
+import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.ui.ConversationFragment;
+import eu.siacs.conversations.ui.adapter.MessageAdapter;
+
+/**
+ * This listener starts loading messages from the server.
+ */
+public class ConversationSwipeRefreshListener implements SwipyRefreshLayout.OnRefreshListener {
+ private List<Message> messageList;
+ private ConversationFragment fragment;
+ private ConversationMoreMessagesLoadedListener listener;
+ private SwipyRefreshLayout swipeLayout;
+
+ public ConversationSwipeRefreshListener(List<Message> messageList, SwipyRefreshLayout swipeLayout, ConversationFragment fragment, ListView messagesView, MessageAdapter messageListAdapter) {
+ this.messageList = messageList;
+ this.fragment = fragment;
+ this.swipeLayout = swipeLayout;
+ this.listener = new ConversationMoreMessagesLoadedListener(swipeLayout, messageList, fragment, messagesView, messageListAdapter);
+ }
+
+ @Override
+ public void onRefresh(SwipyRefreshLayoutDirection direction) {
+ Logging.d(Config.LOGTAG, "Refresh swipe container");
+ Logging.d(Config.LOGTAG, "Refresh direction " + direction);
+ final ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+ if (activity.getSelectedConversation().getAccount().getStatus() != Account.State.DISABLED) {
+ synchronized (this.messageList) {
+ long timestamp;
+ if (SwipyRefreshLayoutDirection.TOP == direction) { // Load history -> messages sent/received before first message in database
+ if (messageList.isEmpty()) {
+ timestamp = System.currentTimeMillis();
+ } else {
+ timestamp = this.messageList.get(0).getTimeSent(); // works only because of the ordering (last msg = first msg in list)
+ }
+ this.listener.setLoadHistory(true);
+ activity.xmppConnectionService.loadMoreMessages(activity.getSelectedConversation(), timestamp, this.listener);
+ } else if (SwipyRefreshLayoutDirection.BOTTOM == direction) { // load messages sent/received between last received or last session establishment and now
+ if (activity.getSelectedConversation().getAccount().isOnlineAndConnected()) {
+ Logging.d("mam", "loading missing messages from mam (last session establishing or last received message)");
+ long lastSessionEstablished = this.getTimestampOfLastSessionEstablished(activity.getSelectedConversation());
+ long lastReceivedMessage = this.getTimestampOfLastReceivedOrTransmittedMessage();
+ long startTimestamp = Math.min(lastSessionEstablished, lastReceivedMessage);
+ MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(activity.getSelectedConversation(), startTimestamp, System.currentTimeMillis(), this.listener);
+ if (query != null) {
+ this.listener.setLoadHistory(false);
+ } else {
+ Logging.d("mam", "no query built - no messages loaded");
+ this.listener.onMoreMessagesLoaded(0, activity.getSelectedConversation());
+ this.listener.informUser(R.string.no_more_history_on_server);
+ }
+ this.listener.informUser(R.string.fetching_history_from_server);
+ } else {
+ this.listener.informUser(R.string.not_connected_try_again);
+ swipeLayout.setRefreshing(false);
+ }
+ }
+ }
+ } else {
+ this.listener.informUser(R.string.this_account_is_disabled);
+ swipeLayout.setRefreshing(false);
+ }
+ Logging.d(Config.LOGTAG, "End Refresh swipe container");
+ }
+
+ private long getTimestampOfLastReceivedOrTransmittedMessage() {
+ long lastReceivedOrTransmittedMessage = Long.MAX_VALUE;
+ if (null != this.messageList
+ && !this.messageList.isEmpty()) {
+ int lastMessageIndex = this.messageList.size() - 1;
+ if (0 <= lastMessageIndex && this.messageList.size() > lastMessageIndex) {
+ lastReceivedOrTransmittedMessage = this.messageList.get(lastMessageIndex).getTimeSent();
+ }
+ }
+
+ return lastReceivedOrTransmittedMessage;
+ }
+
+ private long getTimestampOfLastSessionEstablished(Conversation conversation) {
+ long lastSessionEstablished = Long.MAX_VALUE;
+ if (null != conversation
+ && null != conversation.getAccount()
+ && null != conversation.getAccount().getXmppConnection()) {
+ lastSessionEstablished = conversation.getAccount().getXmppConnection().getLastSessionEstablished();
+ }
+ return lastSessionEstablished;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/widget/Switch.java b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java
deleted file mode 100644
index fd3b5553..00000000
--- a/src/main/java/eu/siacs/conversations/ui/widget/Switch.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package eu.siacs.conversations.ui.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-import com.kyleduo.switchbutton.SwitchButton;
-
-public class Switch extends SwitchButton {
-
- private int mTouchSlop;
- private int mClickTimeout;
- private float mStartX;
- private float mStartY;
- private OnClickListener mOnClickListener;
-
- public Switch(Context context) {
- super(context);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
- }
-
- public Switch(Context context, AttributeSet attrs) {
- super(context, attrs);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
- }
-
- public Switch(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
- }
-
- @Override
- public void setOnClickListener(OnClickListener onClickListener) {
- this.mOnClickListener = onClickListener;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (!isEnabled()) {
- float deltaX = event.getX() - mStartX;
- float deltaY = event.getY() - mStartY;
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mStartX = event.getX();
- mStartY = event.getY();
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- float time = event.getEventTime() - event.getDownTime();
- if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) {
- if (mOnClickListener != null) {
- this.mOnClickListener.onClick(this);
- }
- }
- break;
- default:
- break;
- }
- return true;
- }
- return super.onTouchEvent(event);
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index 306d50c2..1568eb8c 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -7,20 +7,13 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.RouteInfo;
import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
-import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.Random;
-import java.util.TreeMap;
-import java.util.Map;
+import java.util.TreeSet;
import java.util.regex.Pattern;
import de.measite.minidns.Client;
@@ -28,47 +21,58 @@ 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 de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.dto.SrvRecord;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xmpp.jid.Jid;
public class DNSHelper {
-
- public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
- public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+ private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp.";
+ private static final String SECURE_CLIENT_SRV_PREFIX = "_xmpps-client._tcp.";
+ private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
protected static Client client = new Client();
- public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException {
- final String host = jid.getDomainpart();
- final List<InetAddress> servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop();
- Bundle b = new Bundle();
- for(InetAddress server : servers) {
- b = queryDNS(host, server);
- if (b.containsKey("values")) {
- return b;
- }
- }
- if (!b.containsKey("values")) {
- Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record");
- ArrayList<Parcelable> values = new ArrayList<>();
- values.add(createNamePortBundle(host, 5222, false));
- b.putParcelableArrayList("values",values);
- }
- return b;
- }
+ static {
+ client.setTimeout(Config.PING_TIMEOUT * 1000);
+ }
+
+ /**
+ * Queries the SRV record for the server JID.
+ * This method uses all available Domain Name Servers.
+ * @param jid the server JID
+ * @return TreeSet with SrvRecords. If no SRV record is found for JID an empty TreeSet is returned.
+ */
+ public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) {
+ String host = jid.getDomainpart();
+ TreeSet<SrvRecord> result = new TreeSet<>();
+
+ final List<InetAddress> dnsServers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers() : getDnsServersPreLollipop();
+
+ if (dnsServers != null) {
+ for (InetAddress dnsServer : dnsServers) {
+ result = querySrvRecord(host, dnsServer);
+ if (!result.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
@TargetApi(21)
- private static List<InetAddress> getDnsServers(Context context) {
+ private static List<InetAddress> getDnsServers() {
List<InetAddress> servers = new ArrayList<>();
- ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ ConnectivityManager connectivityManager = (ConnectivityManager) ConversationsPlusApplication.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE);
Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
if (networks == null) {
return getDnsServersPreLollipop();
@@ -84,7 +88,7 @@ public class DNSHelper {
}
}
if (servers.size() > 0) {
- Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks");
+ Logging.d("dns", "used lollipop variant to discover dns servers in " + networks.length + " networks");
}
return servers.size() > 0 ? servers : getDnsServersPreLollipop();
}
@@ -112,155 +116,38 @@ public class DNSHelper {
return servers;
}
- private static class TlsSrv {
- private final SRV srv;
- private final boolean tls;
-
- public TlsSrv(SRV srv, boolean tls) {
- this.srv = srv;
- this.tls = tls;
- }
- }
-
- private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException {
- final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
- for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
- for (Record rr : rrset) {
- Data d = rr.getPayload();
- if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
- SRV srv = (SRV) d;
- if (!priorities.containsKey(srv.getPriority())) {
- priorities.put(srv.getPriority(),new ArrayList<TlsSrv>());
- }
- priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls));
- }
- if (d instanceof A) {
- A a = (A) d;
- if (!ips4.containsKey(rr.getName())) {
- ips4.put(rr.getName(), new ArrayList<String>());
- }
- ips4.get(rr.getName()).add(a.toString());
- }
- if (d instanceof AAAA) {
- AAAA aaaa = (AAAA) d;
- if (!ips6.containsKey(rr.getName())) {
- ips6.put(rr.getName(), new ArrayList<String>());
- }
- ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
- }
- }
- }
- }
-
- public static Bundle queryDNS(String host, InetAddress dnsServer) {
- Bundle bundle = new Bundle();
- try {
- client.setTimeout(Config.PING_TIMEOUT * 1000);
- final String qname = "_xmpp-client._tcp." + host;
- final String tlsQname = "_xmpps-client._tcp." + host;
- Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
-
- final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>();
- final Map<String, List<String>> ips4 = new TreeMap<>();
- final Map<String, List<String>> ips6 = new TreeMap<>();
-
- fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false);
- fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true);
-
- final List<TlsSrv> result = new ArrayList<>();
- for (final List<TlsSrv> s : priorities.values()) {
- result.addAll(s);
- }
-
- final ArrayList<Bundle> values = new ArrayList<>();
- if (result.size() == 0) {
- DNSMessage response;
- try {
- response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
- }
- try {
- response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
- }
- values.add(createNamePortBundle(host, 5222, false));
- bundle.putParcelableArrayList("values", values);
- return bundle;
- }
- for (final TlsSrv tlsSrv : result) {
- final SRV srv = tlsSrv.srv;
- if (ips6.containsKey(srv.getName())) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls));
- } else {
- try {
- DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
- }
- }
- if (ips4.containsKey(srv.getName())) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls));
- } else {
- DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
- for(int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls));
- }
- }
- values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls));
- }
- bundle.putParcelableArrayList("values", values);
- } catch (SocketTimeoutException e) {
- bundle.putString("error", "timeout");
- } catch (Exception e) {
- bundle.putString("error", "unhandled");
- }
- return bundle;
- }
-
- private static Bundle createNamePortBundle(String name, int port, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- return namePort;
- }
-
- private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- if (ips!=null) {
- List<String> ip = ips.get(name);
- Collections.shuffle(ip, new Random());
- namePort.putString("ip", ip.get(0));
- }
- return namePort;
- }
-
- private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- if (data instanceof A) {
- namePort.putString("ip", data.toString());
- } else if (data instanceof AAAA) {
- namePort.putString("ip","["+data.toString()+"]");
- }
- return namePort;
- }
+ /**
+ * Queries the SRV record for an host from the given Domain Name Server.
+ * @param host the host to query for
+ * @param dnsServerAddress the DNS to query on
+ * @return TreeSet with SrvRecords.
+ */
+ private static final TreeSet<SrvRecord> querySrvRecord(String host, InetAddress dnsServerAddress) {
+ TreeSet<SrvRecord> result = new TreeSet<>();
+ querySrvRecord(host, dnsServerAddress, false, result);
+ querySrvRecord(host, dnsServerAddress, true, result);
+ return result;
+ }
+
+ private static final void querySrvRecord(String host, InetAddress dnsServerAddress, boolean tlsSrvRecord, TreeSet<SrvRecord> result) {
+ String qname = (tlsSrvRecord ? SECURE_CLIENT_SRV_PREFIX : CLIENT_SRV_PREFIX) + host;
+ String dnsServerHostAddress = dnsServerAddress.getHostAddress();
+ Logging.d("dns", "using dns server: " + dnsServerHostAddress + " to look up " + qname);
+ try {
+ DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerHostAddress);
+ Record[] rrset = message.getAnswers();
+ for (Record rr : rrset) {
+ Data d = rr.getPayload();
+ if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
+ SRV srv = (SRV) d;
+ SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort(), tlsSrvRecord);
+ result.add(srvRecord);
+ }
+ }
+ } catch (IOException e) {
+ Logging.d("dns", "Error while retrieving SRV record '" + qname + "' for '" + host + "' from DNS '" + dnsServerHostAddress + "': " + e.getMessage());
+ }
+ }
public static boolean isIp(final String server) {
return server != null && (
diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
index 8799b4a5..9c8db210 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
@@ -4,13 +4,10 @@ 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;
@@ -18,6 +15,8 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -36,11 +35,9 @@ public class ExceptionHelper {
}
}
- public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) {
+ public static boolean checkForCrash(final ConversationActivity activity, final XmppConnectionService service) {
try {
- final SharedPreferences preferences = PreferenceManager
- .getDefaultSharedPreferences(activity);
- boolean neverSend = preferences.getBoolean("never_send", false);
+ boolean neverSend = ConversationsPlusPreferences.neverSend();
if (neverSend) {
return false;
}
@@ -89,13 +86,13 @@ public class ExceptionHelper {
@Override
public void onClick(DialogInterface dialog, int which) {
- Log.d(Config.LOGTAG, "using account="
+ Logging.d(Config.LOGTAG, "using account="
+ finalAccount.getJid().toBareJid()
+ " to send in stack trace");
Conversation conversation = null;
try {
conversation = service.findOrCreateConversation(finalAccount,
- Jid.fromString("bugs@siacs.eu"), false);
+ Jid.fromString(activity.getString(R.string.cplus_bugreport_jabberid)), false);
} catch (final InvalidJidException ignored) {
}
Message message = new Message(conversation, report
@@ -108,8 +105,7 @@ public class ExceptionHelper {
@Override
public void onClick(DialogInterface dialog, int which) {
- preferences.edit().putBoolean("never_send", true)
- .apply();
+ ConversationsPlusPreferences.applyNeverSend(true);
}
});
builder.create().show();
diff --git a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java b/src/main/java/eu/siacs/conversations/utils/ExifHelper.java
index ceda7293..5e465e94 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExifHelper.java
@@ -16,11 +16,11 @@
package eu.siacs.conversations.utils;
-import android.util.Log;
-
import java.io.IOException;
import java.io.InputStream;
+import de.thedevstack.android.logcat.Logging;
+
public class ExifHelper {
private static final String TAG = "CameraExif";
@@ -56,7 +56,7 @@ public class ExifHelper {
}
length = pack(buf, 0, 2, false);
if (length < 2) {
- Log.e(TAG, "Invalid length");
+ Logging.e(TAG, "Invalid length");
return 0;
}
length -= 2;
@@ -91,7 +91,7 @@ public class ExifHelper {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
- Log.e(TAG, "Invalid byte order");
+ Logging.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
@@ -99,7 +99,7 @@ public class ExifHelper {
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
- Log.e(TAG, "Invalid offset");
+ Logging.e(TAG, "Invalid offset");
return 0;
}
offset += count;
@@ -123,7 +123,7 @@ public class ExifHelper {
case 8:
return 270;
}
- Log.i(TAG, "Unsupported orientation");
+ Logging.i(TAG, "Unsupported orientation");
return 0;
}
offset += 12;
@@ -131,7 +131,7 @@ public class ExifHelper {
}
}
- Log.i(TAG, "Orientation not found");
+ Logging.i(TAG, "Orientation not found");
return 0;
}
diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java
index 6e75d41c..7783bcc9 100644
--- a/src/main/java/eu/siacs/conversations/utils/FileUtils.java
+++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.utils;
import android.annotation.SuppressLint;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
@@ -12,25 +13,26 @@ import android.provider.MediaStore;
import java.io.File;
-public class FileUtils {
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+
+public final class FileUtils {
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
- * @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
@SuppressLint("NewApi")
- public static String getPath(final Context context, final Uri uri) {
+ public static String getPath(final Uri uri) {
if (uri == null) {
return null;
}
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
+ final Context context = ConversationsPlusApplication.getAppContext();
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
@@ -78,7 +80,7 @@ public class FileUtils {
}
}
// MediaStore (and general)
- else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
String path = getDataColumn(context, uri, null, null);
if (path != null) {
File file = new File(path);
@@ -89,7 +91,7 @@ public class FileUtils {
return path;
}
// File
- else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ else if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri.getPath();
}
@@ -106,7 +108,7 @@ public class FileUtils {
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
- public static String getDataColumn(Context context, Uri uri, String selection,
+ private static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
@@ -155,4 +157,37 @@ public class FileUtils {
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
+
+ /**
+ * @param filename The filename to extract extension from
+ * @return last extension or empty string
+ */
+ public static String getLastExtension(final String filename) {
+ if (filename == null || filename.isEmpty()) {
+ return "";
+ }
+ final int lastDotPosition = filename.lastIndexOf('.');
+ final String lastPart = lastDotPosition != -1 ?
+ filename.substring(lastDotPosition + 1) : "";
+ return lastPart;
+ }
+
+ /**
+ * @param filename The filename to extract extension from
+ * @return second to last extension or empty string
+ */
+ public static String getSecondToLastExtension(final String filename) {
+ if (filename == null || filename.isEmpty()) {
+ return "";
+ }
+ final int lastDotPosition = filename.lastIndexOf('.');
+ final int secondToLastDotPosition = filename.lastIndexOf('.', lastDotPosition - 1);
+ final String secondToLastPart = secondToLastDotPosition != -1 ?
+ filename.substring(secondToLastDotPosition + 1, lastDotPosition) : "";
+ return secondToLastPart;
+ }
+
+ private FileUtils() {
+ // Utility class - do not instantiate
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java b/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java
index 8fe67234..5faa1fa7 100644
--- a/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java
+++ b/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.utils;
import android.os.Build;
import android.os.Process;
-import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -19,6 +18,8 @@ import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
+import de.thedevstack.android.logcat.Logging;
+
/**
* Fixes for the output of the default PRNG having low entropy.
*
@@ -209,7 +210,7 @@ public final class PRNGFixes {
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not writable.
// Log and ignore.
- Log.w(PRNGFixes.class.getSimpleName(),
+ Logging.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE);
} finally {
mSeeded = true;
diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
index 768e9f17..04cfa2eb 100644
--- a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
+++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
@@ -43,10 +43,6 @@ public class SocksSocketFactory {
return socket;
}
- public static Socket createSocketOverTor(String destination, int port) throws IOException {
- return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 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 add3d80c..5e4b4acd 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -5,6 +5,7 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Pair;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -15,17 +16,12 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.xmpp.jid.Jid;
public class UIHelper {
- private static String BLACK_HEART_SUIT = "\u2665";
- private static String HEAVY_BLACK_HEART_SUIT = "\u2764";
- private static String WHITE_HEART_SUIT = "\u2661";
-
- public static final ArrayList<String> HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT));
-
private static final ArrayList<String> LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList(
"where are you", //en
"where are you now", //en
@@ -182,9 +178,8 @@ public class UIHelper {
return new Pair<>(getFileDescriptionString(context,message),true);
}
} else {
- if (message.getBody().startsWith(Message.ME_COMMAND)) {
- return new Pair<>(message.getBody().replaceAll("^" + Message.ME_COMMAND,
- UIHelper.getMessageDisplayName(message) + " "), false);
+ if (message.hasMeCommand()) {
+ return new Pair<>(message.getBodyReplacedMeCommand(UIHelper.getMessageDisplayName(message)), false);
} else if (GeoHelper.isGeoUri(message.getBody())) {
if (message.getStatus() == Message.STATUS_RECEIVED) {
return new Pair<>(context.getString(R.string.received_location), true);
@@ -195,7 +190,7 @@ public class UIHelper {
return new Pair<>(context.getString(R.string.x_file_offered_for_download,
getFileDescriptionString(context,message)),true);
} else{
- return new Pair<>(message.getBody().trim(), false);
+ return new Pair<>(message.getBody(), false);
}
}
}
@@ -220,7 +215,7 @@ public class UIHelper {
} else if (mime.contains("vcard")) {
return context.getString(R.string.vcard) ;
} else {
- return mime;
+ return message.getRelativeFilePath();
}
}
@@ -247,13 +242,27 @@ public class UIHelper {
}
}
+ public static String getStatusColor(Presence.Status status) {
+ switch (status) {
+ case ONLINE:
+ case CHAT:
+ return "#259B23";
+ case AWAY:
+ case XA:
+ return "#FF9800";
+ case DND:
+ return "#E51C23";
+ }
+ return "#CCCCCC";
+ }
+
private static String getDisplayedMucCounterpart(final Jid counterpart) {
if (counterpart==null) {
return "";
} else if (!counterpart.isBareJid()) {
- return counterpart.getResourcepart().trim();
+ return counterpart.getResourcepart();
} else {
- return counterpart.toString().trim();
+ return counterpart.toString();
}
}
@@ -263,8 +272,24 @@ public class UIHelper {
|| message.getType() != Message.TYPE_TEXT) {
return false;
}
- String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault());
+ String body = message.getBody() == null ? null : message.getBody().toLowerCase(Locale.getDefault());
body = body.replace("?","").replace("¿","");
return LOCATION_QUESTIONS.contains(body);
}
+
+ public static String getHumanReadableFileSize(long filesize) {
+ if (0 > filesize) {
+ return "?";
+ }
+ double size = Double.valueOf(filesize);
+ String[] sizes = {" bytes", " Kb", " Mb", " Gb", " Tb"};
+ int i = 0;
+ while (1023 < size) {
+ size /= 1024d;
+ ++i;
+ }
+ BigDecimal readableSize = new BigDecimal(size);
+ readableSize = readableSize.setScale(2, BigDecimal.ROUND_HALF_UP);
+ return readableSize.doubleValue() + sizes[i];
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java
index 34794be1..9152c679 100644
--- a/src/main/java/eu/siacs/conversations/xml/Element.java
+++ b/src/main/java/eu/siacs/conversations/xml/Element.java
@@ -1,11 +1,10 @@
package eu.siacs.conversations.xml;
-import android.util.Log;
-
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -128,7 +127,7 @@ public class Element {
try {
return Jid.fromString(jid);
} catch (final InvalidJidException e) {
- Log.e(Config.LOGTAG, "could not parse jid " + jid);
+ Logging.e(Config.LOGTAG, "could not parse jid " + jid);
return null;
}
}
diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java
index aeaaa593..74e65fcd 100644
--- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java
+++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.xml;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
-import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -12,6 +11,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
public class XmlReader {
@@ -25,7 +25,7 @@ public class XmlReader {
this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
true);
} catch (XmlPullParserException e) {
- Log.d(Config.LOGTAG, "error setting namespace feature on parser");
+ Logging.d(Config.LOGTAG, "error setting namespace feature on parser");
}
this.wakeLock = wakeLock;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index fd0e355d..acdf5652 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -39,6 +39,7 @@ import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
+import java.util.TreeSet;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
@@ -49,6 +50,8 @@ import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.dto.SrvRecord;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.crypto.sasl.DigestMd5;
@@ -88,7 +91,7 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
public class XmppConnection implements Runnable {
-
+ private static final int DEFAULT_PORT = 5222;
private static final int PACKET_IQ = 0;
private static final int PACKET_MESSAGE = 1;
private static final int PACKET_PRESENCE = 2;
@@ -227,7 +230,7 @@ public class XmppConnection implements Runnable {
}
protected void connect() {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
features.encryptionEnabled = false;
this.attempt++;
switch (account.getJid().getDomainpart()) {
@@ -246,19 +249,8 @@ public class XmppConnection implements Runnable {
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
this.changeStatus(Account.State.CONNECTING);
- final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
- if (useTor) {
- String destination;
- if (account.getHostname() == null || account.getHostname().isEmpty()) {
- destination = account.getServer().toString();
- } else {
- destination = account.getHostname();
- }
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR");
- socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
- startXmpp();
- } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
+ if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
socket = new Socket();
try {
socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
@@ -269,75 +261,60 @@ public class XmppConnection implements Runnable {
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
try {
- socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
+ socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
startXmpp();
} else {
- final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
- final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
- for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) {
- final Bundle namePort = (Bundle) iterator.next();
- try {
- String srvRecordServer;
- try {
- srvRecordServer = IDN.toASCII(namePort.getString("name"));
- } catch (final IllegalArgumentException e) {
- // TODO: Handle me?`
- srvRecordServer = "";
- }
- final int srvRecordPort = namePort.getInt("port");
- final String srvIpServer = namePort.getString("ip");
- // if tls is true, encryption is implied and must not be started
- features.encryptionEnabled = namePort.getBoolean("tls");
- final InetSocketAddress addr;
- if (srvIpServer != null) {
- addr = new InetSocketAddress(srvIpServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns " + srvRecordServer
- + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
- } else {
- addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
- }
-
- if (!features.encryptionEnabled) {
- socket = new Socket();
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
- } else {
- final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
- socket = tlsFactoryVerifier.factory.createSocket();
-
- if (socket == null) {
- throw new IOException("could not initialize ssl socket");
- }
-
- SSLSocketHelper.setSecurity((SSLSocket) socket);
- SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
- SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
-
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
-
- if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
- throw new SecurityException();
- }
- }
-
- if (startXmpp())
- break; // successfully connected to server that speaks xmpp
- } catch(final SecurityException e) {
- throw e;
- } catch (final Throwable e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
- if (!iterator.hasNext()) {
- throw new UnknownHostException();
- }
- }
- }
+ final TreeSet<SrvRecord> srvRecords = DNSHelper.querySrvRecord(account.getServer());
+ if (srvRecords.isEmpty()) {
+ socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
+ }
+ startXmpp();
+ } else {
+ for (SrvRecord srvRecord : srvRecords) {
+ // if tls is true, encryption is implied and must not be started
+ features.encryptionEnabled = srvRecord.isUseTls();
+ TlsFactoryVerifier tlsFactoryVerifier = null;
+ if (features.encryptionEnabled) {
+ try {
+ tlsFactoryVerifier = getTlsFactoryVerifier();
+ socket = tlsFactoryVerifier.factory.createSocket();
+
+ if (socket == null) {
+ throw new IOException("could not initialize ssl socket");
+ }
+
+ SSLSocketHelper.setSecurity((SSLSocket) socket);
+ SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
+ SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+ } catch (SecurityException e) {
+ throw e;
+ } catch (KeyManagementException e) {
+ Logging.e("connection-init", "Error while creating TLS verifier factory: " + e.getMessage(), e);
+ throw new SecurityException();
+ }
+ } else {
+ socket = new Socket();
+ }
+
+ socket.connect(new InetSocketAddress(srvRecord.getName(), srvRecord.getPort()), Config.SOCKET_TIMEOUT * 1000);
+
+ if (null != tlsFactoryVerifier && !tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
+ throw new SecurityException();
+ }
+
+ if (startXmpp()) {
+ break; // successfully connected to server that speaks xmpp
+ }
+ }
+ }
}
processStream();
} catch (final IncompatibleServerException e) {
@@ -348,10 +325,8 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.UNAUTHORIZED);
} catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
- } catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
- this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
this.changeStatus(Account.State.OFFLINE);
this.attempt--; //don't count attempt when reconnecting instantly anyway
} finally {
@@ -651,7 +626,7 @@ public class XmppConnection implements Runnable {
callback = packetCallbackDuple.second;
packetCallbacks.remove(packet.getId());
} else {
- Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
+ Logging.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
}
}
} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
@@ -758,16 +733,16 @@ public class XmppConnection implements Runnable {
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() +
+ Logging.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");
+ Logging.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
}
- Log.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
+ Logging.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
auth.setAttribute("mechanism", saslMechanism.getMechanism());
if (!saslMechanism.getClientFirstMessage().isEmpty()) {
auth.setContent(saslMechanism.getClientFirstMessage());
@@ -778,7 +753,7 @@ public class XmppConnection implements Runnable {
}
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) {
if (Config.EXTENDED_SM_LOGGING) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
}
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
@@ -850,7 +825,7 @@ public class XmppConnection implements Runnable {
URL uri = new URL(urlString);
captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
} catch (IOException e) {
- Log.e(Config.LOGTAG, e.toString());
+ Logging.e(Config.LOGTAG, e.toString());
}
}
@@ -919,11 +894,11 @@ public class XmppConnection implements Runnable {
sendPostBindInitialization();
}
} else {
- Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
disconnect(true);
}
} else {
- Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure ("+packet.toString());
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure ("+packet.toString());
disconnect(true);
}
}
@@ -983,7 +958,7 @@ public class XmppConnection implements Runnable {
if (packet.getType() == IqPacket.TYPE.RESULT) {
sendPostBindInitialization();
} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
disconnect(true);
}
}
@@ -1074,13 +1049,13 @@ public class XmppConnection implements Runnable {
enableAdvancedStreamFeatures();
}
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
}
if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
mPendingServiceDiscoveries--;
if (mPendingServiceDiscoveries == 0) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery");
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
if (bindListener != null) {
bindListener.onBind(account);
}
@@ -1140,11 +1115,11 @@ public class XmppConnection implements Runnable {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
if (!packet.hasChild("error")) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": successfully enabled carbons");
features.carbonsEnabled = true;
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": error enableing carbons " + packet.toString());
}
}
@@ -1157,11 +1132,11 @@ public class XmppConnection implements Runnable {
if (streamError != null && streamError.hasChild("conflict")) {
final String resource = account.getResource().split("\\.")[0];
account.setResource(resource + "." + nextRandomId());
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
account.getJid().toBareJid() + ": switching resource due to conflict ("
+ account.getResource() + ")");
} else if (streamError != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
}
}
@@ -1285,7 +1260,7 @@ public class XmppConnection implements Runnable {
Thread.sleep(10);
}
socket.close();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
@@ -1295,7 +1270,7 @@ public class XmppConnection implements Runnable {
}).start();
} else {
forceCloseSocket();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
}
}
@@ -1304,13 +1279,13 @@ public class XmppConnection implements Runnable {
try {
socket.close();
} catch (IOException e) {
- e.printStackTrace();
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")");
}
}
}
public void disconnect(final boolean force) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
if (force) {
forceCloseSocket();
return;
@@ -1404,7 +1379,12 @@ public class XmppConnection implements Runnable {
}
public long getLastSessionEstablished() {
- final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
+ final long diff;
+ if (this.lastSessionStarted == 0) {
+ diff = SystemClock.elapsedRealtime() - this.lastConnect;
+ } else {
+ diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
+ }
return System.currentTimeMillis() - diff;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index e3eec647..22f5dca9 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -1,18 +1,24 @@
package eu.siacs.conversations.xmpp.jingle;
-import android.util.Log;
+import android.content.Intent;
+import android.net.Uri;
import android.util.Pair;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback;
@@ -26,6 +32,7 @@ import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -94,7 +101,7 @@ public class JingleConnection implements Transferable {
public void onFileTransmitted(DownloadableFile file) {
if (responder.equals(account.getJid())) {
sendSuccess();
- mXmppConnectionService.getFileBackend().updateFileParams(message);
+ MessageUtil.updateFileParams(message);
mXmppConnectionService.databaseBackend.createMessage(message);
mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED);
if (acceptedAutomatically) {
@@ -106,9 +113,9 @@ public class JingleConnection implements Transferable {
file.delete();
}
}
- Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")");
+ Logging.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")");
if (message.getEncryption() != Message.ENCRYPTION_PGP) {
- mXmppConnectionService.getFileBackend().updateMediaScanner(file);
+ FileBackend.updateMediaScanner(file, mXmppConnectionService);
} else {
account.getPgpDecryptionService().add(message);
}
@@ -134,17 +141,17 @@ public class JingleConnection implements Transferable {
@Override
public void success() {
if (initiator.equals(account.getJid())) {
- Log.d(Config.LOGTAG, "we were initiating. sending file");
+ Logging.d(Config.LOGTAG, "we were initiating. sending file");
transport.send(file, onFileTransmissionSatusChanged);
} else {
transport.receive(file, onFileTransmissionSatusChanged);
- Log.d(Config.LOGTAG, "we were responding. receiving file");
+ Logging.d(Config.LOGTAG, "we were responding. receiving file");
}
}
@Override
public void failed() {
- Log.d(Config.LOGTAG, "proxy activation failed");
+ Logging.d(Config.LOGTAG, "proxy activation failed");
}
};
@@ -190,13 +197,13 @@ public class JingleConnection implements Transferable {
returnResult = this.receiveFallbackToIbb(packet);
} else {
returnResult = false;
- Log.d(Config.LOGTAG, "trying to fallback to something unknown"
+ Logging.d(Config.LOGTAG, "trying to fallback to something unknown"
+ packet.toString());
}
} else if (packet.isAction("transport-accept")) {
returnResult = this.receiveTransportAccept(packet);
} else {
- Log.d(Config.LOGTAG, "packet arrived in connection. action was "
+ Logging.d(Config.LOGTAG, "packet arrived in connection. action was "
+ packet.getAction());
returnResult = false;
}
@@ -258,14 +265,14 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"connection to our own primary candidete failed");
sendInitRequest();
}
@Override
public void established() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"succesfully connected to our own primary candidate");
mergeCandidate(candidate);
sendInitRequest();
@@ -273,7 +280,8 @@ public class JingleConnection implements Transferable {
});
mergeCandidate(candidate);
} else {
- Log.d(Config.LOGTAG, "no primary candidate of our own was found");
+ Logging.d(Config.LOGTAG,
+ "no primary candidate of our own was found");
sendInitRequest();
}
}
@@ -314,64 +322,59 @@ public class JingleConnection implements Transferable {
Element fileSize = fileOffer.findChild("size");
Element fileNameElement = fileOffer.findChild("name");
if (fileNameElement != null) {
- String[] filename = fileNameElement.getContent()
- .toLowerCase(Locale.US).toLowerCase().split("\\.");
- String extension = filename[filename.length - 1];
- if (VALID_IMAGE_EXTENSIONS.contains(extension)) {
- message.setType(Message.TYPE_IMAGE);
- message.setRelativeFilePath(message.getUuid()+"."+extension);
- } else if (VALID_CRYPTO_EXTENSIONS.contains(
- filename[filename.length - 1])) {
- if (filename.length == 3) {
- extension = filename[filename.length - 2];
- if (VALID_IMAGE_EXTENSIONS.contains(extension)) {
- message.setType(Message.TYPE_IMAGE);
- message.setRelativeFilePath(message.getUuid()+"."+extension);
- } else {
- message.setType(Message.TYPE_FILE);
- }
- if (filename[filename.length - 1].equals("otr")) {
- message.setEncryption(Message.ENCRYPTION_OTR);
- } else {
- message.setEncryption(Message.ENCRYPTION_PGP);
+ String filename = fileNameElement.getContent()
+ .toLowerCase(Locale.US).toLowerCase();
+ final String lastPart = FileUtils.getLastExtension(filename);
+ final String secondToLastPart = FileUtils.getSecondToLastExtension(filename);
+ if (!lastPart.isEmpty()) {
+ if (VALID_IMAGE_EXTENSIONS.contains(lastPart)) {
+ message.setType(Message.TYPE_IMAGE);
+ message.setRelativeFilePath(message.getUuid()+"."+lastPart);
+ } else if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
+ if (!secondToLastPart.isEmpty()) {
+ if (VALID_IMAGE_EXTENSIONS.contains(secondToLastPart)) {
+ message.setType(Message.TYPE_IMAGE);
+ message.setRelativeFilePath(message.getUuid()+"."+secondToLastPart);
+ } else {
+ message.setType(Message.TYPE_FILE);
+ message.setRelativeFilePath(message.getUuid() + "_"
+ + filename.substring(0, filename.length() - (secondToLastPart.length() + 1)));
+ }
+ if (lastPart.equals("otr")) {
+ message.setEncryption(Message.ENCRYPTION_OTR);
+ } else {
+ message.setEncryption(Message.ENCRYPTION_PGP);
+ }
}
+ } else {
+ message.setType(Message.TYPE_FILE);
+ message.setRelativeFilePath(message.getUuid() + "_" + filename);
}
} else {
message.setType(Message.TYPE_FILE);
+ message.setRelativeFilePath(message.getUuid() + "_" + filename);
}
- if (message.getType() == Message.TYPE_FILE) {
- String suffix = "";
- if (!fileNameElement.getContent().isEmpty()) {
- String parts[] = fileNameElement.getContent().split("/");
- suffix = parts[parts.length - 1];
- if (message.getEncryption() == Message.ENCRYPTION_OTR && suffix.endsWith(".otr")) {
- suffix = suffix.substring(0,suffix.length() - 4);
- } else if (message.getEncryption() == Message.ENCRYPTION_PGP && (suffix.endsWith(".pgp") || suffix.endsWith(".gpg"))) {
- suffix = suffix.substring(0,suffix.length() - 4);
- }
- }
- message.setRelativeFilePath(message.getUuid()+"_"+suffix);
- }
+
long size = Long.parseLong(fileSize.getContent());
message.setBody(Long.toString(size));
conversation.add(message);
mXmppConnectionService.updateConversationUi();
if (mJingleConnectionManager.hasStoragePermission()
- && size < this.mJingleConnectionManager.getAutoAcceptFileSize()) {
- Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
+ && size <= ConversationsPlusPreferences.autoAcceptFileSize()
+ && mXmppConnectionService.isDownloadAllowedInConnection()) {
+ Logging.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
this.acceptedAutomatically = true;
this.sendAccept();
} else {
message.markUnread();
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"not auto accepting new file offer with size: "
+ size
+ " allowed size:"
- + this.mJingleConnectionManager
- .getAutoAcceptFileSize());
+ + ConversationsPlusPreferences.autoAcceptFileSize());
this.mXmppConnectionService.getNotificationService().push(message);
}
- this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
if (mXmppAxolotlMessage != null) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage);
if (transportMessage != null) {
@@ -380,7 +383,7 @@ public class JingleConnection implements Transferable {
this.file.setIv(transportMessage.getIv());
message.setAxolotlFingerprint(transportMessage.getFingerprint());
} else {
- Log.d(Config.LOGTAG,"could not process KeyTransportMessage");
+ Logging.d(Config.LOGTAG,"could not process KeyTransportMessage");
}
} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
byte[] key = conversation.getSymmetricKey();
@@ -398,7 +401,7 @@ public class JingleConnection implements Transferable {
} else {
this.file.setExpectedSize(size);
}
- Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
+ Logging.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
} else {
this.sendCancel();
this.fail();
@@ -414,13 +417,13 @@ public class JingleConnection implements Transferable {
Content content = new Content(this.contentCreator, this.contentName);
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
content.setTransportId(this.transportId);
- this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
Pair<InputStream,Integer> pair;
try {
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
Conversation conversation = this.message.getConversation();
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key");
cancel();
}
this.file.setKeyAndIv(conversation.getSymmetricKey());
@@ -452,7 +455,7 @@ public class JingleConnection implements Transferable {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
mJingleStatus = JINGLE_STATUS_INITIATED;
mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED);
} else {
@@ -494,7 +497,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG,"connection to our own primary candidate failed");
+ Logging.d(Config.LOGTAG,"connection to our own primary candidate failed");
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
sendJinglePacket(packet);
@@ -503,7 +506,7 @@ public class JingleConnection implements Transferable {
@Override
public void established() {
- Log.d(Config.LOGTAG, "connected to primary candidate");
+ Logging.d(Config.LOGTAG, "connected to primary candidate");
mergeCandidate(candidate);
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
@@ -512,7 +515,7 @@ public class JingleConnection implements Transferable {
}
});
} else {
- Log.d(Config.LOGTAG,"did not find a primary candidate for ourself");
+ Logging.d(Config.LOGTAG,"did not find a primary candidate for ourself");
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
sendJinglePacket(packet);
@@ -558,13 +561,13 @@ public class JingleConnection implements Transferable {
onProxyActivated.success();
} else {
String cid = content.socks5transport().findChild("activated").getAttribute("cid");
- Log.d(Config.LOGTAG, "received proxy activated (" + cid
+ Logging.d(Config.LOGTAG, "received proxy activated (" + cid
+ ")prior to choosing our own transport");
JingleSocks5Transport connection = this.connections.get(cid);
if (connection != null) {
connection.setActivated(true);
} else {
- Log.d(Config.LOGTAG, "activated connection not found");
+ Logging.d(Config.LOGTAG, "activated connection not found");
this.sendCancel();
this.fail();
}
@@ -574,7 +577,7 @@ public class JingleConnection implements Transferable {
onProxyActivated.failed();
return true;
} else if (content.socks5transport().hasChild("candidate-error")) {
- Log.d(Config.LOGTAG, "received candidate error");
+ Logging.d(Config.LOGTAG, "received candidate error");
this.receivedCandidate = true;
if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
&& (this.sentCandidate)) {
@@ -585,7 +588,7 @@ public class JingleConnection implements Transferable {
String cid = content.socks5transport()
.findChild("candidate-used").getAttribute("cid");
if (cid != null) {
- Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
+ Logging.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
JingleCandidate candidate = getCandidate(cid);
candidate.flagAsUsedByCounterpart();
this.receivedCandidate = true;
@@ -593,7 +596,7 @@ public class JingleConnection implements Transferable {
&& (this.sentCandidate)) {
this.connect();
} else {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"ignoring because file is already in transmission or we havent sent our candidate yet");
}
return true;
@@ -612,7 +615,7 @@ public class JingleConnection implements Transferable {
final JingleSocks5Transport connection = chooseConnection();
this.transport = connection;
if (connection == null) {
- Log.d(Config.LOGTAG, "could not find suitable candidate");
+ Logging.d(Config.LOGTAG, "could not find suitable candidate");
this.disconnectSocks5Connections();
if (this.initiator.equals(account.getJid())) {
this.sendFallbackToIbb();
@@ -621,7 +624,7 @@ public class JingleConnection implements Transferable {
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
if (connection.needsActivation()) {
if (connection.getCandidate().isOurs()) {
- Log.d(Config.LOGTAG, "candidate "
+ Logging.d(Config.LOGTAG, "candidate "
+ connection.getCandidate().getCid()
+ " was our proxy. going to activate");
IqPacket activation = new IqPacket(IqPacket.TYPE.SET);
@@ -645,17 +648,17 @@ public class JingleConnection implements Transferable {
}
});
} else {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"candidate "
+ connection.getCandidate().getCid()
+ " was a proxy. waiting for other party to activate");
}
} else {
if (initiator.equals(account.getJid())) {
- Log.d(Config.LOGTAG, "we were initiating. sending file");
+ Logging.d(Config.LOGTAG, "we were initiating. sending file");
connection.send(file, onFileTransmissionSatusChanged);
} else {
- Log.d(Config.LOGTAG, "we were responding. receiving file");
+ Logging.d(Config.LOGTAG, "we were responding. receiving file");
connection.receive(file, onFileTransmissionSatusChanged);
}
}
@@ -667,11 +670,11 @@ public class JingleConnection implements Transferable {
for (Entry<String, JingleSocks5Transport> cursor : connections
.entrySet()) {
JingleSocks5Transport currentConnection = cursor.getValue();
- // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
+ // Logging.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
if (currentConnection.isEstablished()
&& (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
.getCandidate().isOurs()))) {
- // Log.d(Config.LOGTAG,"is usable");
+ // Logging.d(Config.LOGTAG,"is usable");
if (connection == null) {
connection = currentConnection;
} else {
@@ -680,7 +683,7 @@ public class JingleConnection implements Transferable {
connection = currentConnection;
} else if (connection.getCandidate().getPriority() == currentConnection
.getCandidate().getPriority()) {
- // Log.d(Config.LOGTAG,"found two candidates with same priority");
+ // Logging.d(Config.LOGTAG,"found two candidates with same priority");
if (initiator.equals(account.getJid())) {
if (currentConnection.getCandidate().isOurs()) {
connection = currentConnection;
@@ -712,7 +715,7 @@ public class JingleConnection implements Transferable {
}
private void sendFallbackToIbb() {
- Log.d(Config.LOGTAG, "sending fallback to ibb");
+ Logging.d(Config.LOGTAG, "sending fallback to ibb");
JinglePacket packet = this.bootstrapPacket("transport-replace");
Content content = new Content(this.contentCreator, this.contentName);
this.transportId = this.mJingleConnectionManager.nextRandomId();
@@ -724,7 +727,7 @@ public class JingleConnection implements Transferable {
}
private boolean receiveFallbackToIbb(JinglePacket packet) {
- Log.d(Config.LOGTAG, "receiving fallack to ibb");
+ Logging.d(Config.LOGTAG, "receiving fallack to ibb");
String receivedBlockSize = packet.getJingleContent().ibbTransport()
.getAttribute("block-size");
if (receivedBlockSize != null) {
@@ -760,7 +763,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG, "ibb open failed");
+ Logging.d(Config.LOGTAG, "ibb open failed");
}
@Override
@@ -812,8 +815,8 @@ public class JingleConnection implements Transferable {
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
this.transport.disconnect();
}
- FileBackend.close(mFileInputStream);
- FileBackend.close(mFileOutputStream);
+ StreamUtil.close(mFileInputStream);
+ StreamUtil.close(mFileOutputStream);
if (this.message != null) {
if (this.responder.equals(account.getJid())) {
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
@@ -857,7 +860,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"connection failed with " + candidate.getHost() + ":"
+ candidate.getPort());
connectNextCandidate();
@@ -865,7 +868,7 @@ public class JingleConnection implements Transferable {
@Override
public void established() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"established connection with " + candidate.getHost()
+ ":" + candidate.getPort());
sendCandidateUsed(candidate.getCid());
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
index 0f0361cd..f4a069bc 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -1,7 +1,6 @@
package eu.siacs.conversations.xmpp.jingle;
import android.annotation.SuppressLint;
-import android.util.Log;
import java.math.BigInteger;
import java.security.SecureRandom;
@@ -9,6 +8,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message;
@@ -155,9 +155,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
}
- Log.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString());
+ Logging.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString());
} else {
- Log.d(Config.LOGTAG, "no sid found in incoming ibb packet");
+ Logging.d(Config.LOGTAG, "no sid found in incoming ibb packet");
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
index 0b0cb408..3800b94f 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
@@ -1,7 +1,6 @@
package eu.siacs.conversations.xmpp.jingle;
import android.util.Base64;
-import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
@@ -10,10 +9,11 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -95,13 +95,13 @@ public class JingleInbandTransport extends JingleTransport {
file.createNewFile();
this.fileOutputStream = connection.getFileOutputStream();
if (this.fileOutputStream == null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
callback.onFileTransferAborted();
return;
}
this.remainingSize = this.fileSize = file.getExpectedSize();
} catch (final NoSuchAlgorithmException | IOException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage());
callback.onFileTransferAborted();
}
}
@@ -118,7 +118,7 @@ public class JingleInbandTransport extends JingleTransport {
this.digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
callback.onFileTransferAborted();
return;
}
@@ -127,7 +127,7 @@ public class JingleInbandTransport extends JingleTransport {
}
} catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
}
}
@@ -186,8 +186,8 @@ public class JingleInbandTransport extends JingleTransport {
fileInputStream.close();
}
} catch (IOException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
- FileBackend.close(fileInputStream);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ StreamUtil.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
@@ -210,8 +210,8 @@ public class JingleInbandTransport extends JingleTransport {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
}
} catch (IOException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
- FileBackend.close(fileOutputStream);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ StreamUtil.close(fileOutputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
index 9240bd2c..76cd0c87 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
@@ -1,25 +1,22 @@
package eu.siacs.conversations.xmpp.jingle;
import android.os.PowerManager;
-import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.InetAddress;
import java.net.InetSocketAddress;
-import java.net.Proxy;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SocksSocketFactory;
@@ -62,14 +59,10 @@ public class JingleSocks5Transport extends JingleTransport {
@Override
public void run() {
try {
- final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
- if (useTor) {
- socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(),candidate.getPort());
- } else {
- socket = new Socket();
- SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
- socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
- }
+ socket = new Socket();
+ SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
+ socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
+
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
SocksSocketFactory.createSocksConnection(socket,destination,0);
@@ -98,7 +91,7 @@ public class JingleSocks5Transport extends JingleTransport {
digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
callback.onFileTransferAborted();
return;
}
@@ -118,16 +111,16 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransmitted(file);
}
} catch (FileNotFoundException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (IOException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} finally {
- FileBackend.close(fileInputStream);
+ StreamUtil.close(fileInputStream);
wakeLock.release();
}
}
@@ -153,7 +146,7 @@ public class JingleSocks5Transport extends JingleTransport {
fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
return;
}
double size = file.getExpectedSize();
@@ -164,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
count = inputStream.read(buffer);
if (count == -1) {
callback.onFileTransferAborted();
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
return;
} else {
fileOutputStream.write(buffer, 0, count);
@@ -178,18 +171,18 @@ public class JingleSocks5Transport extends JingleTransport {
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
callback.onFileTransmitted(file);
} catch (FileNotFoundException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (IOException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} finally {
wakeLock.release();
- FileBackend.close(fileOutputStream);
- FileBackend.close(inputStream);
+ StreamUtil.close(fileOutputStream);
+ StreamUtil.close(inputStream);
}
}
}).start();
@@ -204,9 +197,9 @@ public class JingleSocks5Transport extends JingleTransport {
}
public void disconnect() {
- FileBackend.close(inputStream);
- FileBackend.close(outputStream);
- FileBackend.close(socket);
+ StreamUtil.close(inputStream);
+ StreamUtil.close(outputStream);
+ StreamUtil.close(socket);
}
public boolean isEstablished() {