aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations/services/XmppConnectionService.java')
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java1444
1 files changed, 1111 insertions, 333 deletions
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 5aca32d0..09a409fc 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -6,11 +6,16 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.IBinder;
@@ -18,7 +23,13 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
+import android.preference.PreferenceManager;
import android.provider.ContactsContract;
+import android.security.KeyChain;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LruCache;
+import android.util.Pair;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
@@ -26,17 +37,22 @@ import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus;
+import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.math.BigInteger;
import java.security.SecureRandom;
+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;
import java.util.Hashtable;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -58,6 +74,8 @@ import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Bookmark;
@@ -68,6 +86,9 @@ import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
+import eu.siacs.conversations.entities.Presences;
+import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.generator.MessageGenerator;
import eu.siacs.conversations.generator.PresenceGenerator;
@@ -83,11 +104,13 @@ import eu.siacs.conversations.utils.ExceptionHelper;
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;
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
@@ -102,6 +125,7 @@ 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;
@@ -115,6 +139,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
public static final String ACTION_TRY_AGAIN = "try_again";
public static final String ACTION_DISABLE_ACCOUNT = "disable_account";
+ private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
+ 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 List<String> mInProgressAvatarFetches = new ArrayList<>();
+ public DatabaseBackend databaseBackend;
private ContentObserver contactObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
@@ -125,59 +157,30 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
startService(intent);
}
};
-
- private final IBinder mBinder = new XmppConnectionBinder();
- private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
- private final FileObserver fileObserver = new FileObserver(
- FileBackend.getConversationsImageDirectory()) {
-
- @Override
- public void onEvent(int event, String path) {
- if (event == FileObserver.DELETE) {
- markFileDeleted(path.split("\\.")[0]);
- }
- }
- };
- private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
-
- @Override
- public void onJinglePacketReceived(Account account, JinglePacket packet) {
- mJingleConnectionManager.deliverPacket(account, packet);
- }
- };
- private final OnBindListener mOnBindListener = new OnBindListener() {
-
- @Override
- public void onBind(final Account account) {
- account.getRoster().clearPresences();
- account.pendingConferenceJoins.clear();
- account.pendingConferenceLeaves.clear();
- fetchRosterFromServer(account);
- fetchBookmarks(account);
- sendPresence(account);
- connectMultiModeConversations(account);
- updateConversationUi();
- }
- };
- private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
-
+ private FileBackend fileBackend = new FileBackend(this);
+ private MemorizingTrustManager mMemorizingTrustManager;
+ private NotificationService mNotificationService = new NotificationService(
+ this);
+ private OnMessagePacketReceived mMessageParser = new MessageParser(this);
+ private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
+ private IqParser mIqParser = new IqParser(this);
+ private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() {
@Override
- public void onMessageAcknowledged(Account account, String uuid) {
- for (final Conversation conversation : getConversations()) {
- if (conversation.getAccount() == account) {
- Message message = conversation.findUnsentMessageWithUuid(uuid);
- if (message != null) {
- markMessage(message, Message.STATUS_SEND);
- if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) {
- databaseBackend.updateConversation(conversation);
- }
- }
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
+ Element error = packet.findChild("error");
+ String text = error != null ? error.findChildContent("text") : null;
+ if (text != null) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text);
}
}
}
};
- private final IqGenerator mIqGenerator = new IqGenerator();
- public DatabaseBackend databaseBackend;
+ private MessageGenerator mMessageGenerator = new MessageGenerator(this);
+ private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
+ private List<Account> accounts;
+ private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
+ this);
public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
@Override
@@ -204,38 +207,73 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
};
- private MemorizingTrustManager mMemorizingTrustManager;
- private NotificationService mNotificationService = new NotificationService(
+ private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
this);
- private OnMessagePacketReceived mMessageParser = new MessageParser(this);
- private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
- private IqParser mIqParser = new IqParser(this);
- private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() {
+ private AvatarService mAvatarService = new AvatarService(this);
+ private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
+ private OnConversationUpdate mOnConversationUpdate = null;
+ private final FileObserver fileObserver = new FileObserver(
+ FileBackend.getConversationsImageDirectory()) {
+
@Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Element error = packet.findChild("error");
- String text = error != null ? error.findChildContent("text") : null;
- if (text != null) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received iq error - "+text);
+ public void onEvent(int event, String path) {
+ if (event == FileObserver.DELETE) {
+ markFileDeleted(path.split("\\.")[0]);
+ }
+ }
+ };
+ private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
+
+ @Override
+ public void onJinglePacketReceived(Account account, JinglePacket packet) {
+ mJingleConnectionManager.deliverPacket(account, packet);
+ }
+ };
+ private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
+
+ @Override
+ public void onMessageAcknowledged(Account account, String uuid) {
+ for (final Conversation conversation : getConversations()) {
+ if (conversation.getAccount() == account) {
+ Message message = conversation.findUnsentMessageWithUuid(uuid);
+ if (message != null) {
+ markMessage(message, Message.STATUS_SEND);
+ }
}
}
}
};
- private MessageGenerator mMessageGenerator = new MessageGenerator();
- private PresenceGenerator mPresenceGenerator = new PresenceGenerator();
- private List<Account> accounts;
- private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
- this);
- private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
- this);
- private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
- private OnConversationUpdate mOnConversationUpdate = null;
private int convChangedListenerCount = 0;
private OnShowErrorToast mOnShowErrorToast = null;
private int showErrorToastListenerCount = 0;
private int unreadCount = -1;
private OnAccountUpdate mOnAccountUpdate = null;
+ private OnCaptchaRequested mOnCaptchaRequested = null;
+ private int accountChangedListenerCount = 0;
+ private int captchaRequestedListenerCount = 0;
+ private OnRosterUpdate mOnRosterUpdate = null;
+ private OnUpdateBlocklist mOnUpdateBlocklist = null;
+ private int updateBlocklistListenerCount = 0;
+ private int rosterChangedListenerCount = 0;
+ private OnMucRosterUpdate mOnMucRosterUpdate = null;
+ private int mucRosterChangedListenerCount = 0;
+ private OnKeyStatusUpdated mOnKeyStatusUpdated = null;
+ private int keyStatusUpdatedListenerCount = 0;
+ private SecureRandom mRandom;
+ private final OnBindListener mOnBindListener = new OnBindListener() {
+
+ @Override
+ public void onBind(final Account account) {
+ account.getRoster().clearPresences();
+ mJingleConnectionManager.cancelInTransmission();
+ fetchRosterFromServer(account);
+ fetchBookmarks(account);
+ sendPresence(account);
+ mMessageArchiveService.executePendingQueries(account);
+ connectMultiModeConversations(account);
+ syncDirtyContacts(account);
+ }
+ };
private OnStatusChanged statusListener = new OnStatusChanged() {
@Override
@@ -245,14 +283,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mOnAccountUpdate.onAccountUpdate();
}
if (account.getStatus() == Account.State.ONLINE) {
- for (Conversation conversation : account.pendingConferenceLeaves) {
- leaveMuc(conversation);
- }
- for (Conversation conversation : account.pendingConferenceJoins) {
- joinMuc(conversation);
+ if (connection != null && connection.getFeatures().csi()) {
+ if (checkListeners()) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive");
+ connection.sendInactive();
+ } else {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active");
+ connection.sendActive();
+ }
}
- mMessageArchiveService.executePendingQueries(account);
- mJingleConnectionManager.cancelInTransmission();
List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
if (conversation.getAccount() == account) {
@@ -260,28 +299,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendUnsentMessages(conversation);
}
}
- if (connection != null && connection.getFeatures().csi()) {
- if (checkListeners()) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid()
- + " sending csi//inactive");
- connection.sendInactive();
- } else {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid()
- + " sending csi//active");
- connection.sendActive();
- }
+ for (Conversation conversation : account.pendingConferenceLeaves) {
+ leaveMuc(conversation);
}
- syncDirtyContacts(account);
- scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode());
+ account.pendingConferenceLeaves.clear();
+ for (Conversation conversation : account.pendingConferenceJoins) {
+ joinMuc(conversation);
+ }
+ account.pendingConferenceJoins.clear();
+ scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
} else if (account.getStatus() == Account.State.OFFLINE) {
resetSendingToWaiting(account);
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- int timeToReconnect = mRandom.nextInt(50) + 10;
- scheduleWakeUpCall(timeToReconnect,account.getUuid().hashCode());
+ int timeToReconnect = mRandom.nextInt(20) + 10;
+ scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
}
} else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
databaseBackend.updateAccount(account);
- reconnectAccount(account, true);
+ reconnectAccount(account, true, false);
} else if ((account.getStatus() != Account.State.CONNECTING)
&& (account.getStatus() != Account.State.NO_INTERNET)) {
if (connection != null) {
@@ -290,27 +325,26 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
+ ": error connecting account. try again in "
+ next + "s for the "
+ (connection.getAttempt() + 1) + " time");
- scheduleWakeUpCall(next,account.getUuid().hashCode());
+ scheduleWakeUpCall(next, account.getUuid().hashCode());
}
- }
+ }
getNotificationService().updateErrorNotification();
}
};
- private int accountChangedListenerCount = 0;
- private OnRosterUpdate mOnRosterUpdate = null;
- private OnUpdateBlocklist mOnUpdateBlocklist = null;
- private int updateBlocklistListenerCount = 0;
- private int rosterChangedListenerCount = 0;
- private OnMucRosterUpdate mOnMucRosterUpdate = null;
- private int mucRosterChangedListenerCount = 0;
- private SecureRandom mRandom;
private OpenPgpServiceConnection pgpServiceConnection;
private PgpEngine mPgpEngine = null;
private WakeLock wakeLock;
private PowerManager pm;
+ private LruCache<String, Bitmap> mBitmapCache;
private Thread mPhoneContactMergerThread;
+ private EventReceiver mEventReceiver = new EventReceiver();
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;
}
@@ -319,8 +353,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (pgpServiceConnection.isBound()) {
if (this.mPgpEngine == null) {
this.mPgpEngine = new PgpEngine(new OpenPgpApi(
- getApplicationContext(),
- pgpServiceConnection.getService()), this);
+ getApplicationContext(),
+ pgpServiceConnection.getService()), this);
}
return mPgpEngine;
} else {
@@ -329,14 +363,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
+ public FileBackend getFileBackend() {
+ return this.fileBackend;
+ }
+
+ public AvatarService getAvatarService() {
+ return this.mAvatarService;
+ }
+
public void attachLocationToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
- int encryption = conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption());
+ int encryption = conversation.getNextEncryption();
if (encryption == Message.ENCRYPTION_PGP) {
encryption = Message.ENCRYPTION_DECRYPTED;
}
- Message message = new Message(conversation,uri.toString(),encryption);
+ Message message = new Message(conversation, uri.toString(), encryption);
if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart());
}
@@ -348,16 +390,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void attachFileToConversation(final Conversation conversation,
- final Uri uri,
- final UiCallback<Message> callback) {
+ final Uri uri,
+ final UiCallback<Message> callback) {
final Message message;
- boolean forceEncryption = ConversationsPlusPreferences.forceEncryption();
- if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "",
- Message.ENCRYPTION_DECRYPTED);
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
} else {
- message = new Message(conversation, "",
- conversation.getNextEncryption(forceEncryption));
+ message = new Message(conversation, "", conversation.getNextEncryption());
}
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_FILE);
@@ -390,7 +429,41 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public Conversation find(Bookmark bookmark) {
+ 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());
+ }
+ 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) {
return find(bookmark.getAccount(), bookmark.getJid());
}
@@ -401,6 +474,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = intent == null ? null : intent.getAction();
+ boolean interactive = false;
if (action != null) {
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
@@ -410,9 +484,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
break;
case ACTION_MERGE_PHONE_CONTACTS:
if (mRestoredFromDatabase) {
- PhoneHelper.loadPhoneContacts(getApplicationContext(),
- new CopyOnWriteArrayList<Bundle>(),
- this);
+ loadPhoneContacts();
}
return START_STICKY;
case Intent.ACTION_SHUTDOWN:
@@ -427,19 +499,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
break;
case ACTION_TRY_AGAIN:
resetAllAttemptCounts(false);
+ interactive = true;
break;
case ACTION_DISABLE_ACCOUNT:
try {
String jid = intent.getStringExtra("account");
Account account = jid == null ? null : findAccountByJid(Jid.fromString(jid));
if (account != null) {
- account.setOption(Account.OPTION_DISABLED,true);
+ account.setOption(Account.OPTION_DISABLED, true);
updateAccount(account);
}
} catch (final InvalidJidException ignored) {
break;
}
break;
+ case AudioManager.RINGER_MODE_CHANGED_ACTION:
+ if (xaOnSilentMode()) {
+ refreshAllPresences();
+ }
+ break;
+ case Intent.ACTION_SCREEN_OFF:
+ case Intent.ACTION_SCREEN_ON:
+ if (awayWhenScreenOff()) {
+ refreshAllPresences();
+ }
+ break;
}
}
this.wakeLock.acquire();
@@ -462,36 +546,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
long lastReceived = account.getXmppConnection().getLastPacketReceived();
long lastSent = account.getXmppConnection().getLastPingSent();
long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
- long msToNextPing = (Math.max(lastReceived,lastSent) + pingInterval) - SystemClock.elapsedRealtime();
+ long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime();
long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime();
if (lastSent > lastReceived) {
if (pingTimeoutIn < 0) {
Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
- this.reconnectAccount(account, true);
+ this.reconnectAccount(account, true, interactive);
} else {
int secs = (int) (pingTimeoutIn / 1000);
- this.scheduleWakeUpCall(secs,account.getUuid().hashCode());
+ this.scheduleWakeUpCall(secs, account.getUuid().hashCode());
}
} else if (msToNextPing <= 0) {
account.getXmppConnection().sendPing();
- Logging.d(Config.LOGTAG, account.getJid().toBareJid()+" send ping");
- this.scheduleWakeUpCall(Config.PING_TIMEOUT,account.getUuid().hashCode());
+ 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());
}
} else if (account.getStatus() == Account.State.OFFLINE) {
- reconnectAccount(account,true);
+ reconnectAccount(account, true, interactive);
} else if (account.getStatus() == Account.State.CONNECTING) {
- long timeout = Config.CONNECT_TIMEOUT - ((SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000);
+ long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000;
+ long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000;
+ long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
+ long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
if (timeout < 0) {
Logging.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
- reconnectAccount(account, true);
+ reconnectAccount(account, true, interactive);
+ } else if (discoTimeout < 0) {
+ account.getXmppConnection().sendDiscoTimeout();
+ scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
} else {
- scheduleWakeUpCall((int) timeout,account.getUuid().hashCode());
+ scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
}
} else {
if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
- reconnectAccount(account, true);
+ reconnectAccount(account, true, interactive);
}
}
@@ -501,10 +591,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- /*PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
- if (!pm.isScreenOn()) {
- removeStaleListeners();
- }*/
if (wakeLock.isHeld()) {
try {
wakeLock.release();
@@ -514,9 +600,50 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return START_STICKY;
}
+ private boolean xaOnSilentMode() {
+ return getPreferences().getBoolean("xa_on_silent_mode", false);
+ }
+
+ private boolean awayWhenScreenOff() {
+ return getPreferences().getBoolean("away_when_screen_off", false);
+ }
+
+ private String getCompressPicturesPreference() {
+ return getPreferences().getString("picture_compression", "auto");
+ }
+
+ private int getTargetPresence() {
+ if (xaOnSilentMode() && isPhoneSilenced()) {
+ return Presences.XA;
+ } else if (awayWhenScreenOff() && !isInteractive()) {
+ return Presences.AWAY;
+ } else {
+ return Presences.ONLINE;
+ }
+ }
+
+ @SuppressLint("NewApi")
+ @SuppressWarnings("deprecation")
+ public boolean isInteractive() {
+ final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+
+ final boolean isScreenOn;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ isScreenOn = pm.isScreenOn();
+ } else {
+ isScreenOn = pm.isInteractive();
+ }
+ return isScreenOn;
+ }
+
+ private boolean isPhoneSilenced() {
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT;
+ }
+
private void resetAllAttemptCounts(boolean reallyAll) {
Logging.d(Config.LOGTAG, "resetting all attepmt counts");
- for(Account account : accounts) {
+ for (Account account : accounts) {
if (account.hasErrorStatus() || reallyAll) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
@@ -528,7 +655,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public boolean hasInternetConnection() {
ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
- .getSystemService(Context.CONNECTIVITY_SERVICE);
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnected();
}
@@ -559,25 +686,77 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
PRNGFixes.apply();
this.mRandom = new SecureRandom();
updateMemorizingTrustmanager();
+ final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ final int cacheSize = maxMemory / 8;
+ this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
+ @Override
+ protected int sizeOf(final String key, final Bitmap bitmap) {
+ return bitmap.getByteCount() / 1024;
+ }
+ };
this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
this.accounts = databaseBackend.getAccounts();
- for (final Account account : this.accounts) {
- account.initAccountServices(this);
- }
restoreFromDatabase();
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
this.fileObserver.startWatching();
- this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain");
+
+ this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() {
+ @Override
+ public void onBound(IOpenPgpService2 service) {
+ for (Account account : accounts) {
+ if (account.getPgpDecryptionService() != null) {
+ account.getPgpDecryptionService().onOpenPgpServiceBound();
+ }
+ }
+ }
+
+ @Override
+ public void onError(Exception e) { }
+ });
this.pgpServiceConnection.bindToService();
this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"XmppConnectionService");
+ this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService");
toggleForegroundService();
updateUnreadCountBadge();
UiUpdateHelper.initXmppConnectionService(this);
+ toggleScreenEventReceiver();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ if (level >= TRIM_MEMORY_COMPLETE) {
+ Log.d(Config.LOGTAG, "clear cache due to low memory");
+ getBitmapCache().evictAll();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ try {
+ unregisterReceiver(this.mEventReceiver);
+ } catch (IllegalArgumentException e) {
+ //ignored
+ }
+ super.onDestroy();
+ }
+
+ public void toggleScreenEventReceiver() {
+ if (awayWhenScreenOff()) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(this.mEventReceiver, filter);
+ } else {
+ try {
+ unregisterReceiver(this.mEventReceiver);
+ } catch (IllegalArgumentException e) {
+ //ignored
+ }
+ }
}
public void toggleForegroundService() {
@@ -600,7 +779,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (final Account account : accounts) {
databaseBackend.writeRoster(account.getRoster());
if (account.getXmppConnection() != null) {
- disconnect(account, false);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ disconnect(account, false);
+ }
+ }).start();
+
}
}
Context context = getApplicationContext();
@@ -612,7 +797,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
stopSelf();
}
- protected void scheduleWakeUpCall(int seconds, int requestCode) {
+ public void scheduleWakeUpCall(int seconds, int requestCode) {
final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000;
Context context = getApplicationContext();
@@ -635,6 +820,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
connection.setOnBindListener(this.mOnBindListener);
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
+ AxolotlService axolotlService = account.getAxolotlService();
+ if (axolotlService != null) {
+ connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
+ }
return connection;
}
@@ -645,37 +834,40 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- private void sendFileMessage(final Message message) {
+ private void sendFileMessage(final Message message, final boolean delay) {
Logging.d(Config.LOGTAG, "send file message");
final Account account = message.getConversation().getAccount();
final XmppConnection connection = account.getXmppConnection();
if (connection != null && connection.getFeatures().httpUpload()) {
- mHttpConnectionManager.createNewUploadConnection(message);
+ mHttpConnectionManager.createNewUploadConnection(message, delay);
} else {
mJingleConnectionManager.createNewConnection(message);
}
}
public void sendMessage(final Message message) {
- sendMessage(message, false);
+ sendMessage(message, false, false);
}
- private void sendMessage(final Message message, final boolean resend) {
+ private void sendMessage(final Message message, final boolean resend, final boolean delay) {
final Account account = message.getConversation().getAccount();
final Conversation conversation = message.getConversation();
account.deactivateGracePeriod();
MessagePacket packet = null;
- boolean saveInDb = true;
+ final boolean addToConversation = conversation.getMode() != Conversation.MODE_MULTI
+ || account.getServerIdentity() != XmppConnection.Identity.SLACK;
+ boolean saveInDb = addToConversation;
message.setStatus(Message.STATUS_WAITING);
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
message.getConversation().endOtrIfNeeded();
- message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- markMessage(message,Message.STATUS_SEND_FAILED);
- }
- });
+ message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
+ new Conversation.OnMessageFound() {
+ @Override
+ public void onMessageFound(Message message) {
+ markMessage(message, Message.STATUS_SEND_FAILED);
+ }
+ });
}
if (account.isOnlineAndConnected()) {
@@ -683,24 +875,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
case Message.ENCRYPTION_NONE:
if (message.needsUploading()) {
if (account.httpUploadAvailable() || message.fixCounterpart()) {
- this.sendFileMessage(message);
+ this.sendFileMessage(message, delay);
} else {
break;
}
} else {
- packet = mMessageGenerator.generateChat(message,resend);
+ packet = mMessageGenerator.generateChat(message);
}
break;
case Message.ENCRYPTION_PGP:
case Message.ENCRYPTION_DECRYPTED:
if (message.needsUploading()) {
if (account.httpUploadAvailable() || message.fixCounterpart()) {
- this.sendFileMessage(message);
+ this.sendFileMessage(message, delay);
} else {
break;
}
} else {
- packet = mMessageGenerator.generatePgpChat(message,resend);
+ packet = mMessageGenerator.generatePgpChat(message);
}
break;
case Message.ENCRYPTION_OTR:
@@ -714,7 +906,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (message.needsUploading()) {
mJingleConnectionManager.createNewConnection(message);
} else {
- packet = mMessageGenerator.generateOtrChat(message,resend);
+ packet = mMessageGenerator.generateOtrChat(message);
}
} else if (otrSession == null) {
if (message.fixCounterpart()) {
@@ -724,6 +916,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
break;
+ case Message.ENCRYPTION_AXOLOTL:
+ message.setAxolotlFingerprint(account.getAxolotlService().getOwnFingerprint());
+ if (message.needsUploading()) {
+ if (account.httpUploadAvailable() || message.fixCounterpart()) {
+ this.sendFileMessage(message, delay);
+ } else {
+ break;
+ }
+ } else {
+ XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
+ if (axolotlMessage == null) {
+ account.getAxolotlService().preparePayloadMessage(message, delay);
+ } else {
+ packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
+ }
+ }
+ break;
+
}
if (packet != null) {
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
@@ -733,7 +943,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
} else {
- switch(message.getEncryption()) {
+ switch (message.getEncryption()) {
case Message.ENCRYPTION_DECRYPTED:
if (!message.needsUploading()) {
String pgpBody = message.getEncryptedBody();
@@ -751,25 +961,33 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
conversation.startOtrSession(message.getCounterpart().getResourcepart(), false);
}
break;
+ case Message.ENCRYPTION_AXOLOTL:
+ message.setAxolotlFingerprint(account.getAxolotlService().getOwnFingerprint());
+ break;
}
}
if (resend) {
- if (packet != null) {
+ if (packet != null && addToConversation) {
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
- markMessage(message,Message.STATUS_UNSEND);
+ markMessage(message, Message.STATUS_UNSEND);
} else {
- markMessage(message,Message.STATUS_SEND);
+ markMessage(message, Message.STATUS_SEND);
}
}
} else {
- conversation.add(message);
- if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted())) {
+ if (addToConversation) {
+ conversation.add(message);
+ }
+ if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages())) {
databaseBackend.createMessage(message);
}
updateConversationUi();
}
if (packet != null) {
+ if (delay) {
+ mMessageGenerator.addDelay(packet, message.getTimeSent());
+ }
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
if (ConversationsPlusPreferences.chatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
@@ -784,13 +1002,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onMessageFound(Message message) {
- resendMessage(message);
+ resendMessage(message, true);
}
});
}
- public void resendMessage(final Message message) {
- sendMessage(message, true);
+ public void resendMessage(final Message message, final boolean delay) {
+ sendMessage(message, true, delay);
}
public void fetchRosterFromServer(final Account account) {
@@ -813,34 +1031,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
- final Element query = packet.query();
- final List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
- final Element storage = query.findChild("storage",
- "storage:bookmarks");
- if (storage != null) {
- for (final Element item : storage.getChildren()) {
- if (item.getName().equals("conference")) {
- final Bookmark bookmark = Bookmark.parse(item, account);
- bookmarks.add(bookmark);
- Conversation conversation = find(bookmark);
- if (conversation != null) {
- conversation.setBookmark(bookmark);
- } else if (bookmark.autojoin() && bookmark.getJid() != null) {
- conversation = findOrCreateConversation(
- account, bookmark.getJid(), true);
- conversation.setBookmark(bookmark);
- joinMuc(conversation);
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ final Element query = packet.query();
+ final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
+ final Element storage = query.findChild("storage", "storage:bookmarks");
+ final boolean autojoin = respectAutojoin();
+ if (storage != null) {
+ for (final Element item : storage.getChildren()) {
+ if (item.getName().equals("conference")) {
+ final Bookmark bookmark = Bookmark.parse(item, account);
+ Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
+ if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
+ bookmark.setBookmarkName(old.getBookmarkName());
+ }
+ Conversation conversation = find(bookmark);
+ if (conversation != null) {
+ conversation.setBookmark(bookmark);
+ } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) {
+ conversation = findOrCreateConversation(
+ account, bookmark.getJid(), true);
+ conversation.setBookmark(bookmark);
+ joinMuc(conversation);
+ }
}
}
}
+ account.setBookmarks(new ArrayList<>(bookmarks.values()));
+ } else {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks");
}
- account.setBookmarks(bookmarks);
}
};
sendIqPacket(account, iqPacket, callback);
}
public void pushBookmarks(Account account) {
+ 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");
@@ -857,7 +1083,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mPhoneContactMergerThread = new Thread(new Runnable() {
@Override
public void run() {
- Logging.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) {
@@ -873,8 +1099,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
final Contact contact = account.getRoster().getContact(jid);
String systemAccount = phoneContact.getInt("phoneid")
- + "#"
- + phoneContact.getString("lookup");
+ + "#"
+ + phoneContact.getString("lookup");
contact.setSystemAccount(systemAccount);
if (contact.setPhotoUri(phoneContact.getString("photouri"))) {
AvatarService.getInstance().clear(contact);
@@ -882,7 +1108,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
contact.setSystemName(phoneContact.getString("displayname"));
withSystemAccounts.remove(contact);
}
- for(Contact contact : withSystemAccounts) {
+ for (Contact contact : withSystemAccounts) {
contact.setSystemAccount(null);
contact.setSystemName(null);
if (contact.setPhotoUri(null)) {
@@ -908,23 +1134,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Account account = accountLookupTable.get(conversation.getAccountUuid());
conversation.setAccount(account);
}
- Runnable runnable =new Runnable() {
+ Runnable runnable = new Runnable() {
@Override
public void run() {
- Logging.d(Config.LOGTAG,"restoring roster");
- for(Account account : accounts) {
+ 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
}
ImageUtil.evictBitmapCache();
Looper.prepare();
- PhoneHelper.loadPhoneContacts(getApplicationContext(),
- new CopyOnWriteArrayList<Bundle>(),
- XmppConnectionService.this);
- Logging.d(Config.LOGTAG,"restoring messages");
+ loadPhoneContacts();
+ Logging.d(Config.LOGTAG, "restoring messages");
for (Conversation conversation : conversations) {
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
checkDeletedFiles(conversation);
+ conversation.findUnreadMessages(new Conversation.OnMessageFound() {
+ @Override
+ public void onMessageFound(Message message) {
+ mNotificationService.pushFromBacklog(message);
+ }
+ });
}
+ mNotificationService.finishBacklog(false);
mRestoredFromDatabase = true;
Logging.d(Config.LOGTAG,"restored all messages");
updateConversationUi();
@@ -934,6 +1166,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void loadPhoneContacts() {
+ PhoneHelper.loadPhoneContacts(getApplicationContext(),
+ new CopyOnWriteArrayList<Bundle>(),
+ XmppConnectionService.this);
+ }
+
public List<Conversation> getConversations() {
return this.conversations;
}
@@ -945,6 +1183,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onMessageFound(Message 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) {
+ markMessage(message, Message.STATUS_SEND_FAILED);
+ }
}
}
});
@@ -956,7 +1198,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (message != null) {
if (!FileBackend.isFileAvailable(message)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- updateConversationUi();
+ final int s = message.getStatus();
+ if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
+ markMessage(message, Message.STATUS_SEND_FAILED);
+ } else {
+ updateConversationUi();
+ }
}
return;
}
@@ -996,12 +1243,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) {
- Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp));
- if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) {
+ if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) {
Logging.d("mam", "Query in progress");
return;
}
//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() {
@@ -1009,30 +1256,32 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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);
+ final Account account = conversation.getAccount();
+ List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp);
Logging.d("mam", "runnable load more messages");
- if (messages.size() > 0) {
+ 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()
- && account.getXmppConnection().getFeatures().mam()) {
+ conversation.addAll(0, messages);
+ checkDeletedFiles(conversation);
+ callback.onMoreMessagesLoaded(messages.size(), conversation);
+ } else if (conversation.hasMessagesLeftOnServer()
+ && account.isOnlineAndConnected()) {
Logging.d("mam", "mam activate, account online and connected and messages left on server");
- MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp - 1);
- if (query != null) {
- query.setCallback(callback);
- }
- callback.informUser(R.string.fetching_history_from_server);
- } else {
+ 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 - 1);
+ if (query != null) {
+ query.setCallback(callback);
+ }
+ callback.informUser(R.string.fetching_history_from_server);
+ }
+ } else {
Logging.d("mam", ((!conversation.hasMessagesLeftOnServer()) ? "no" : "") + " more messages left on server, mam " + ((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);
}
+ }
}
- }
};
ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
@@ -1130,7 +1379,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
Bookmark bookmark = conversation.getBookmark();
- if (bookmark != null && bookmark.autojoin()) {
+ if (bookmark != null && bookmark.autojoin() && respectAutojoin()) {
bookmark.setAutojoin(false);
pushBookmarks(bookmark.getAccount());
}
@@ -1138,6 +1387,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
leaveMuc(conversation);
} else {
conversation.endOtrIfNeeded();
+ if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ Log.d(Config.LOGTAG, "Canceling presence request from " + conversation.getJid().toString());
+ sendPresencePacket(
+ conversation.getAccount(),
+ mPresenceGenerator.stopPresenceUpdatesTo(conversation.getContact())
+ );
+ }
}
this.databaseBackend.updateConversation(conversation);
this.conversations.remove(conversation);
@@ -1153,10 +1409,68 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateAccountUi();
}
+ public void createAccountFromKey(final String alias, final OnAccountCreated callback) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias);
+ Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]);
+ if (findAccountByJid(info.first) == null) {
+ Account account = new Account(info.first, "");
+ account.setPrivateKeyAlias(alias);
+ account.setOption(Account.OPTION_DISABLED, true);
+ account.setDisplayName(info.second);
+ createAccount(account);
+ callback.onAccountCreated(account);
+ if (Config.X509_VERIFICATION) {
+ try {
+ getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA");
+ } catch (CertificateException e) {
+ callback.informUser(R.string.certificate_chain_is_not_trusted);
+ }
+ }
+ } else {
+ callback.informUser(R.string.account_already_exists);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ callback.informUser(R.string.unable_to_parse_certificate);
+ }
+ }
+ }).start();
+
+ }
+
+ public void updateKeyInAccount(final Account account, final String alias) {
+ Log.d(Config.LOGTAG, "update key in account " + alias);
+ try {
+ X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias);
+ Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]);
+ if (account.getJid().toBareJid().equals(info.first)) {
+ account.setPrivateKeyAlias(alias);
+ account.setDisplayName(info.second);
+ databaseBackend.updateAccount(account);
+ if (Config.X509_VERIFICATION) {
+ try {
+ getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA");
+ } catch (CertificateException e) {
+ showErrorToastInUi(R.string.certificate_chain_is_not_trusted);
+ }
+ account.getAxolotlService().regenerateKeys(true);
+ }
+ } else {
+ showErrorToastInUi(R.string.jid_does_not_match_certificate);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
public void updateAccount(final Account account) {
this.statusListener.onStatusChanged(account);
databaseBackend.updateAccount(account);
- reconnectAccount(account, false);
+ reconnectAccountInBackground(account);
updateAccountUi();
getNotificationService().updateErrorNotification();
}
@@ -1192,7 +1506,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (account.getXmppConnection() != null) {
this.disconnect(account, true);
}
- databaseBackend.deleteAccount(account);
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ databaseBackend.deleteAccount(account);
+ }
+ };
+ ConversationsPlusApplication.executeDatabaseOperation.execute(runnable);
this.accounts.remove(account);
updateAccountUi();
getNotificationService().updateErrorNotification();
@@ -1277,6 +1597,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) {
+ synchronized (this) {
+ if (checkListeners()) {
+ switchToForeground();
+ }
+ this.mOnCaptchaRequested = listener;
+ if (this.captchaRequestedListenerCount < 2) {
+ this.captchaRequestedListenerCount++;
+ }
+ }
+ }
+
+ public void removeOnCaptchaRequestedListener() {
+ synchronized (this) {
+ this.captchaRequestedListenerCount--;
+ if (this.captchaRequestedListenerCount <= 0) {
+ this.mOnCaptchaRequested = null;
+ this.captchaRequestedListenerCount = 0;
+ if (checkListeners()) {
+ switchToBackground();
+ }
+ }
+ }
+ }
+
public void setOnRosterUpdateListener(final OnRosterUpdate listener) {
synchronized (this) {
if (checkListeners()) {
@@ -1327,6 +1672,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) {
+ synchronized (this) {
+ if (checkListeners()) {
+ switchToForeground();
+ }
+ this.mOnKeyStatusUpdated = listener;
+ if (this.keyStatusUpdatedListenerCount < 2) {
+ this.keyStatusUpdatedListenerCount++;
+ }
+ }
+ }
+
+ public void removeOnNewKeysAvailableListener() {
+ synchronized (this) {
+ this.keyStatusUpdatedListenerCount--;
+ if (this.keyStatusUpdatedListenerCount <= 0) {
+ this.keyStatusUpdatedListenerCount = 0;
+ this.mOnKeyStatusUpdated = null;
+ if (checkListeners()) {
+ switchToBackground();
+ }
+ }
+ }
+ }
+
public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
synchronized (this) {
if (checkListeners()) {
@@ -1356,11 +1726,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return (this.mOnAccountUpdate == null
&& this.mOnConversationUpdate == null
&& this.mOnRosterUpdate == null
+ && this.mOnCaptchaRequested == null
&& this.mOnUpdateBlocklist == null
- && this.mOnShowErrorToast == null);
+ && this.mOnShowErrorToast == null
+ && this.mOnKeyStatusUpdated == null);
}
private void switchToForeground() {
+ for (Conversation conversation : getConversations()) {
+ conversation.setIncomingChatState(ChatState.ACTIVE);
+ }
for (Account account : getAccounts()) {
if (account.getStatus() == Account.State.ONLINE) {
XmppConnection connection = account.getXmppConnection();
@@ -1381,9 +1756,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- for(Conversation conversation : getConversations()) {
- conversation.setIncomingChatState(ChatState.ACTIVE);
- }
this.mNotificationService.setIsInForeground(false);
Logging.d(Config.LOGTAG, "app switched into background");
}
@@ -1391,45 +1763,74 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private void connectMultiModeConversations(Account account) {
List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
- if ((conversation.getMode() == Conversation.MODE_MULTI)
- && (conversation.getAccount() == account)) {
- conversation.resetMucOptions();
- joinMuc(conversation);
+ if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) {
+ joinMuc(conversation, true, null);
}
}
}
public void joinMuc(Conversation conversation) {
+ joinMuc(conversation, false, null);
+ }
+
+ private void joinMuc(Conversation conversation, boolean now, final OnConferenceJoined onConferenceJoined) {
Account account = conversation.getAccount();
account.pendingConferenceJoins.remove(conversation);
account.pendingConferenceLeaves.remove(conversation);
- if (account.getStatus() == Account.State.ONLINE) {
- final String nick = conversation.getMucOptions().getProposedNick();
- final Jid joinJid = conversation.getMucOptions().createJoinJid(nick);
- if (joinJid == null) {
- return; //safety net
- }
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString());
- PresencePacket packet = new PresencePacket();
- packet.setFrom(conversation.getAccount().getJid());
- packet.setTo(joinJid);
- Element x = packet.addChild("x", "http://jabber.org/protocol/muc");
- if (conversation.getMucOptions().getPassword() != null) {
- x.addChild("password").setContent(conversation.getMucOptions().getPassword());
- }
- x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted()));
- String sig = account.getPgpSignature();
- if (sig != null) {
- packet.addChild("status").setContent("online");
- packet.addChild("x", "jabber:x:signed").setContent(sig);
- }
- sendPresencePacket(account, packet);
- fetchConferenceConfiguration(conversation);
- if (!joinJid.equals(conversation.getJid())) {
- conversation.setContactJid(joinJid);
- databaseBackend.updateConversation(conversation);
- }
- conversation.setHasMessagesLeftOnServer(false);
+ if (account.getStatus() == Account.State.ONLINE || now) {
+ conversation.resetMucOptions();
+ fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() {
+
+ private void join(Conversation conversation) {
+ Account account = conversation.getAccount();
+ final String nick = conversation.getMucOptions().getProposedNick();
+ final Jid joinJid = conversation.getMucOptions().createJoinJid(nick);
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString());
+ PresencePacket packet = new PresencePacket();
+ packet.setFrom(conversation.getAccount().getJid());
+ packet.setTo(joinJid);
+ Element x = packet.addChild("x", "http://jabber.org/protocol/muc");
+ if (conversation.getMucOptions().getPassword() != null) {
+ x.addChild("password").setContent(conversation.getMucOptions().getPassword());
+ }
+
+ if (conversation.getMucOptions().mamSupport()) {
+ // Use MAM instead of the limited muc history to get history
+ x.addChild("history").setAttribute("maxchars", "0");
+ } else {
+ // Fallback to muc history
+ x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted()));
+ }
+ String sig = account.getPgpSignature();
+ if (sig != null) {
+ packet.addChild("x", "jabber:x:signed").setContent(sig);
+ }
+ sendPresencePacket(account, packet);
+ if (onConferenceJoined != null) {
+ onConferenceJoined.onConferenceJoined(conversation);
+ }
+ if (!joinJid.equals(conversation.getJid())) {
+ conversation.setContactJid(joinJid);
+ databaseBackend.updateConversation(conversation);
+ }
+ conversation.setHasMessagesLeftOnServer(false);
+ if (conversation.getMucOptions().mamSupport()) {
+ getMessageArchiveService().catchupMUC(conversation);
+ }
+ }
+
+ @Override
+ public void onConferenceConfigurationFetched(Conversation conversation) {
+ join(conversation);
+ }
+
+ @Override
+ public void onFetchFailed(final Conversation conversation, Element error) {
+ join(conversation);
+ fetchConferenceConfiguration(conversation);
+ }
+ });
+
} else {
account.pendingConferenceJoins.add(conversation);
}
@@ -1439,7 +1840,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password);
if (conversation.getBookmark() != null) {
- conversation.getBookmark().setAutojoin(true);
+ if (respectAutojoin()) {
+ conversation.getBookmark().setAutojoin(true);
+ }
pushBookmarks(conversation.getAccount());
}
databaseBackend.updateConversation(conversation);
@@ -1497,10 +1900,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void leaveMuc(Conversation conversation) {
+ leaveMuc(conversation, false);
+ }
+
+ private void leaveMuc(Conversation conversation, boolean now) {
Account account = conversation.getAccount();
account.pendingConferenceJoins.remove(conversation);
account.pendingConferenceLeaves.remove(conversation);
- if (account.getStatus() == Account.State.ONLINE) {
+ if (account.getStatus() == Account.State.ONLINE || now) {
PresencePacket packet = new PresencePacket();
packet.setTo(conversation.getJid());
packet.setFrom(conversation.getAccount().getJid());
@@ -1548,34 +1955,37 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
String name = new BigInteger(75, getRNG()).toString(32);
Jid jid = Jid.fromParts(name, server, null);
final Conversation conversation = findOrCreateConversation(account, jid, true);
- joinMuc(conversation);
- Bundle options = new Bundle();
- options.putString("muc#roomconfig_persistentroom", "1");
- options.putString("muc#roomconfig_membersonly", "1");
- options.putString("muc#roomconfig_publicroom", "0");
- options.putString("muc#roomconfig_whois", "anyone");
- pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() {
+ joinMuc(conversation, true, new OnConferenceJoined() {
@Override
- public void onPushSucceeded() {
- for (Jid invite : jids) {
- invite(conversation, invite);
- }
- if (account.countPresences() > 1) {
- directInvite(conversation, account.getJid().toBareJid());
- }
- if (callback != null) {
- callback.success(conversation);
- }
- }
+ public void onConferenceJoined(final Conversation conversation) {
+ Bundle options = new Bundle();
+ options.putString("muc#roomconfig_persistentroom", "1");
+ options.putString("muc#roomconfig_membersonly", "1");
+ options.putString("muc#roomconfig_publicroom", "0");
+ options.putString("muc#roomconfig_whois", "anyone");
+ pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() {
+ @Override
+ public void onPushSucceeded() {
+ for (Jid invite : jids) {
+ invite(conversation, invite);
+ }
+ if (account.countPresences() > 1) {
+ directInvite(conversation, account.getJid().toBareJid());
+ }
+ if (callback != null) {
+ callback.success(conversation);
+ }
+ }
- @Override
- public void onPushFailed() {
- if (callback != null) {
- callback.error(R.string.conference_creation_failed, conversation);
- }
+ @Override
+ public void onPushFailed() {
+ if (callback != null) {
+ callback.error(R.string.conference_creation_failed, conversation);
+ }
+ }
+ });
}
});
-
} catch (InvalidJidException e) {
if (callback != null) {
callback.error(R.string.conference_creation_failed, null);
@@ -1589,15 +1999,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void fetchConferenceConfiguration(final Conversation conversation) {
+ fetchConferenceConfiguration(conversation, null);
+ }
+
+ public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) {
IqPacket request = new IqPacket(IqPacket.TYPE.GET);
request.setTo(conversation.getJid().toBareJid());
request.query("http://jabber.org/protocol/disco#info");
sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() != IqPacket.TYPE.ERROR) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
ArrayList<String> features = new ArrayList<>();
- for (Element child : packet.query().getChildren()) {
+ Element query = packet.query();
+ for (Element child : query.getChildren()) {
if (child != null && child.getName().equals("feature")) {
String var = child.getAttribute("var");
if (var != null) {
@@ -1605,8 +2020,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
+ Element form = query.findChild("x", "jabber:x:data");
+ if (form != null) {
+ conversation.getMucOptions().updateFormData(Data.parse(form));
+ }
conversation.getMucOptions().updateFeatures(features);
+ if (callback != null) {
+ callback.onConferenceConfigurationFetched(conversation);
+ }
updateConversationUi();
+ } else if (packet.getType() == IqPacket.TYPE.ERROR) {
+ if (callback != null) {
+ callback.onFetchFailed(conversation, packet.getError());
+ }
}
}
});
@@ -1619,11 +2045,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() != IqPacket.TYPE.ERROR) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
for (Field field : data.getFields()) {
- if (options.containsKey(field.getName())) {
- field.setValue(options.getString(field.getName()));
+ if (options.containsKey(field.getFieldName())) {
+ field.setValue(options.getString(field.getFieldName()));
}
}
data.submit();
@@ -1633,12 +2059,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendIqPacket(account, set, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- if (callback != null) {
+ if (callback != null) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
callback.onPushSucceeded();
- }
- } else {
- if (callback != null) {
+ } else {
callback.onPushFailed();
}
}
@@ -1707,7 +2131,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
});
}
- public void disconnect(Account account, boolean force) {
+ private void disconnect(Account account, boolean force) {
if ((account.getStatus() == Account.State.ONLINE)
|| (account.getStatus() == Account.State.DISABLED)) {
if (!force) {
@@ -1715,7 +2139,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Conversation conversation : conversations) {
if (conversation.getAccount() == account) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
- leaveMuc(conversation);
+ leaveMuc(conversation, true);
} else {
if (conversation.endOtrIfNeeded()) {
Logging.d(Config.LOGTAG, account.getJid().toBareJid()
@@ -1767,7 +2191,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
account.getJid().toBareJid() + " otr session established with "
+ conversation.getJid() + "/"
+ otrSession.getSessionID().getUserID());
- conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
+ conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
@Override
public void onMessageFound(Message message) {
@@ -1780,8 +2204,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (message.needsUploading()) {
mJingleConnectionManager.createNewConnection(message);
} else {
- MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true);
+ MessagePacket outPacket = mMessageGenerator.generateOtrChat(message);
if (outPacket != null) {
+ mMessageGenerator.addDelay(outPacket, message.getTimeSent());
message.setStatus(Message.STATUS_SEND);
databaseBackend.updateMessage(message);
sendMessagePacket(account, outPacket);
@@ -1801,8 +2226,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_CHAT);
packet.setFrom(account.getJid());
- packet.addChild("private", "urn:xmpp:carbons:2");
- packet.addChild("no-copy", "urn:xmpp:hints");
+ MessageGenerator.addMessageHints(packet);
packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
+ otrSession.getSessionID().getUserID());
try {
@@ -1842,6 +2266,221 @@ 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);
@@ -1860,24 +2499,39 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.databaseBackend.updateConversation(conversation);
}
- public void reconnectAccount(final Account account, final boolean force) {
+ private void reconnectAccount(final Account account, final boolean force, final boolean interactive) {
synchronized (account) {
- if (account.getXmppConnection() != null) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
disconnect(account, force);
+ } else {
+ connection = createConnection(account);
+ account.setXmppConnection(connection);
}
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
-
- AvatarService.getInstance().clearFetchInProgress(account);
-
- if (account.getXmppConnection() == null) {
- account.setXmppConnection(createConnection(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();
+ }
+ }
+ }
+ if (!force) {
+ try {
+ Logging.d(Config.LOGTAG, "wait for disconnect");
+ Thread.sleep(500); //sleep wait for disconnect
+ } catch (InterruptedException e) {
+ //ignored
+ }
}
- Thread thread = new Thread(account.getXmppConnection());
+ Thread thread = new Thread(connection);
+ connection.setInteractive(interactive);
thread.start();
- scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode());
+ scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
} else {
account.getRoster().clearPresences();
- account.setXmppConnection(null);
+ connection.resetEverything();
}
}
}
@@ -1886,7 +2540,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
new Thread(new Runnable() {
@Override
public void run() {
- reconnectAccount(account,false);
+ reconnectAccount(account, false, true);
}
}).start();
}
@@ -1922,7 +2576,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
for (Conversation conversation : getConversations()) {
if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) {
- final Message message = conversation.findSentMessageWithUuid(uuid);
+ final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid);
if (message != null) {
markMessage(message, status);
}
@@ -1932,8 +2586,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return null;
}
- public boolean markMessage(Conversation conversation, String uuid,
- int status) {
+ public boolean markMessage(Conversation conversation, String uuid, int status) {
if (uuid == null) {
return false;
} else {
@@ -1958,9 +2611,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateConversationUi();
}
+ public SharedPreferences getPreferences() {
+ return PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+ }
+
+ public boolean confirmMessages() {
+ return getPreferences().getBoolean("confirm_messages", true);
+ }
+
+ public boolean sendChatStates() {
+ return getPreferences().getBoolean("chat_states", false);
+ }
+
+ public boolean saveEncryptedMessages() {
+ return !getPreferences().getBoolean("dont_save_encrypted", false);
+ }
+
+ private boolean respectAutojoin() {
+ 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);
+ }
+
public int unreadCount() {
int count = 0;
- for(Conversation conversation : getConversations()) {
+ for (Conversation conversation : getConversations()) {
count += conversation.unreadCount();
}
return count;
@@ -1991,6 +2677,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public boolean displayCaptchaRequest(Account account, String id, Data data, Bitmap captcha) {
+ boolean rc = false;
+ if (mOnCaptchaRequested != null) {
+ DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
+ Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int) (captcha.getWidth() * metrics.scaledDensity),
+ (int) (captcha.getHeight() * metrics.scaledDensity), false);
+
+ mOnCaptchaRequested.onCaptchaRequested(account, id, data, scaled);
+ rc = true;
+ }
+
+ return rc;
+ }
+
public void updateBlocklistUi(final OnUpdateBlocklist.Status status) {
if (mOnUpdateBlocklist != null) {
mOnUpdateBlocklist.OnUpdateBlocklist(status);
@@ -2003,6 +2703,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void keyStatusUpdated(AxolotlService.FetchStatus report) {
+ if (mOnKeyStatusUpdated != null) {
+ mOnKeyStatusUpdated.onKeyStatusUpdated(report);
+ }
+ }
+
public Account findAccountByJid(final Jid accountJid) {
for (Account account : this.accounts) {
if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {
@@ -2023,7 +2729,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void markRead(final Conversation conversation) {
mNotificationService.clear(conversation);
- conversation.markRead();
+ final List<Message> readMessages = conversation.markRead();
+ if (readMessages.size() > 0) {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ for (Message message : readMessages) {
+ databaseBackend.updateMessage(message);
+ }
+ }
+ };
+ ConversationsPlusApplication.executeDatabaseOperation.execute(runnable);
+ }
updateUnreadCountBadge();
}
@@ -2079,6 +2796,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.pm;
}
+ public LruCache<String, Bitmap> getBitmapCache() {
+ return this.mBitmapCache;
+ }
+
public void syncRosterToDisk(final Account account) {
Runnable runnable = new Runnable() {
@@ -2129,15 +2850,36 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void sendPresencePacket(Account account, PresencePacket packet) {
- XmppSendUtil.sendPresencePacket(account, packet);
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendPresencePacket(packet);
+ }
+ }
+
+ public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendCaptchaRegistryRequest(id, data);
+ }
}
public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) {
- XmppSendUtil.sendIqPacket(account, packet, callback);
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendIqPacket(packet, callback);
+ }
}
public void sendPresence(final Account account) {
- sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
+ sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence()));
+ }
+
+ public void refreshAllPresences() {
+ for (Account account : getAccounts()) {
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ sendPresence(account);
+ }
+ }
}
public void sendOfflinePresence(final Account account) {
@@ -2201,8 +2943,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
for (final Message msg : messages) {
+ msg.setTime(System.currentTimeMillis());
markMessage(msg, Message.STATUS_WAITING);
- this.resendMessage(msg);
+ this.resendMessage(msg, false);
}
}
@@ -2214,12 +2957,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
* Therefore set this flag to true and try to get messages from server
*/
conversation.setHasMessagesLeftOnServer(true);
- new Thread(new Runnable() {
+ Runnable runnable = new Runnable() {
@Override
public void run() {
databaseBackend.deleteMessagesInConversation(conversation);
}
- }).start();
+ };
+ ConversationsPlusPreferences.dontTrustSystemCAs().execute(runnable);
}
public void sendBlockRequest(final Blockable blockable) {
@@ -2253,54 +2997,88 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public interface OnMoreMessagesLoaded {
- public void onMoreMessagesLoaded(int count, Conversation conversation);
+ public void publishDisplayName(Account account) {
+ String displayName = account.getDisplayName();
+ if (displayName != null && !displayName.isEmpty()) {
+ IqPacket publish = mIqGenerator.publishNick(displayName);
+ sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @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");
+ }
+ }
+ });
+ }
+ }
- public void informUser(int r);
+ public interface OnAccountCreated {
+ void onAccountCreated(Account account);
- void setLoadingInProgress();
+ void informUser(int r);
+ }
- boolean isLoadingInProgress();
+ public interface OnMoreMessagesLoaded {
+ void onMoreMessagesLoaded(int count, Conversation conversation);
+
+ void informUser(int r);
}
public interface OnAccountPasswordChanged {
- public void onPasswordChangeSucceeded();
+ void onPasswordChangeSucceeded();
- public void onPasswordChangeFailed();
+ void onPasswordChangeFailed();
}
public interface OnAffiliationChanged {
- public void onAffiliationChangedSuccessful(Jid jid);
+ void onAffiliationChangedSuccessful(Jid jid);
- public void onAffiliationChangeFailed(Jid jid, int resId);
+ void onAffiliationChangeFailed(Jid jid, int resId);
}
public interface OnRoleChanged {
- public void onRoleChangedSuccessful(String nick);
+ void onRoleChangedSuccessful(String nick);
- public void onRoleChangeFailed(String nick, int resid);
+ void onRoleChangeFailed(String nick, int resid);
}
public interface OnConversationUpdate {
- public void onConversationUpdate();
+ void onConversationUpdate();
}
public interface OnAccountUpdate {
- public void onAccountUpdate();
+ void onAccountUpdate();
+ }
+
+ public interface OnCaptchaRequested {
+ void onCaptchaRequested(Account account,
+ String id,
+ Data data,
+ Bitmap captcha);
}
public interface OnRosterUpdate {
- public void onRosterUpdate();
+ void onRosterUpdate();
}
public interface OnMucRosterUpdate {
- public void onMucRosterUpdate();
+ void onMucRosterUpdate();
+ }
+
+ public interface OnConferenceConfigurationFetched {
+ void onConferenceConfigurationFetched(Conversation conversation);
+
+ void onFetchFailed(Conversation conversation, Element error);
+ }
+
+ public interface OnConferenceJoined {
+ void onConferenceJoined(Conversation conversation);
}
public interface OnConferenceOptionsPushed {
- public void onPushSucceeded();
+ void onPushSucceeded();
- public void onPushFailed();
+ void onPushFailed();
}
public interface OnShowErrorToast {