mirror of
https://github.com/moparisthebest/Conversations
synced 2024-08-13 15:53:48 -04:00
af7a64491f
Fixes #791 Squash of commits: 534f25d7dae3ce6852243e28fdd0a69ac01e9463 808fdf5147f27a912a60bee39aa4bf1ddd4f43b4 1eaf8a8330710ad35ba7c368e04f909af623ae4c 31585242c2359efdcd0eeddb9745077f54dbc9eb 2e69bd0bd0286ed1e98a42f4c3421ba4d8cf524b e904fb5015bf3a1904ab941a1957edf3b1e7abd2 eebbadf3b3816bbf8fcccb763e419fed252d266f 7c5b87724ce494e5a6e8026557ed50a8fd9f23e8 b0eaaf446937794fe19cbdb4f8309c3ff83d4e42 8c652f9e8bb3512958d9ad8c6f1326505f2d98c8 ad0ea1ad948ff6f8fde7b0b10f5163dc8852032f f5d49897e0dba691ef53a0eddb9ed34d129ad442 a08fa64c505bd895b7c626cfad182380373be20b de67079113e08394a276048c31f6b21baa300829 9069f342173ba30c2b20c67529c7ff497a6a257d 0169fa79d161ee898c4b6762e207087682a952d8 8585a5bd75a5d56927fed8317729bd15fffe4dcc 0053528a078369e0b65dcf71bda251072a1299c7 e901a9c3554bd7cca193e92919b463991eadfea7 c5c78257434813c69ab9b7558bcc8f7cbe858433 e905af348d46d77bc46b5f7211527684acc02fab 13a0f9a10c7892b0f90f5fabd2f2615701b0fd66 2cfba1e24b0139839e4453b92be7e20634d150cf 58e074fb5bb44b05a8104250fccd7c024c808c1a 0d6cf98fc8eab212d798ac79b336f9b70a14f06d e23620f56b85bcab9f3b5d9ce1c01524cd9674dc d72cd2fcc8d54176c3ff53411a69b9bb4642eff3 195143dff8836623a37094a6b8fa6aa01ef31580 5f5f3caf3a1e480a99d27ee5c34ba516419c52e4 1dee3d5861c9f9c710da4cbda3688d94c622ca93 23949b8aa32c78b27bab49bb3c4f3ff588925ce1 9bf97f8ae522796e0dacb7f6fe7a7f90f86a93a1
2209 lines
72 KiB
Java
2209 lines
72 KiB
Java
package eu.siacs.conversations.services;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.AlarmManager;
|
|
import android.app.PendingIntent;
|
|
import android.app.Service;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.database.ContentObserver;
|
|
import android.graphics.Bitmap;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.NetworkInfo;
|
|
import android.net.Uri;
|
|
import android.os.Binder;
|
|
import android.os.Bundle;
|
|
import android.os.FileObserver;
|
|
import android.os.IBinder;
|
|
import android.os.PowerManager;
|
|
import android.os.PowerManager.WakeLock;
|
|
import android.os.SystemClock;
|
|
import android.preference.PreferenceManager;
|
|
import android.provider.ContactsContract;
|
|
import android.util.Log;
|
|
import android.util.LruCache;
|
|
|
|
import net.java.otr4j.OtrException;
|
|
import net.java.otr4j.session.Session;
|
|
import net.java.otr4j.session.SessionID;
|
|
import net.java.otr4j.session.SessionStatus;
|
|
|
|
import org.openintents.openpgp.util.OpenPgpApi;
|
|
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
|
|
|
import java.math.BigInteger;
|
|
import java.security.SecureRandom;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Hashtable;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
|
|
import de.duenndns.ssl.MemorizingTrustManager;
|
|
import eu.siacs.conversations.Config;
|
|
import eu.siacs.conversations.R;
|
|
import eu.siacs.conversations.crypto.PgpEngine;
|
|
import eu.siacs.conversations.entities.Account;
|
|
import eu.siacs.conversations.entities.Blockable;
|
|
import eu.siacs.conversations.entities.Bookmark;
|
|
import eu.siacs.conversations.entities.Contact;
|
|
import eu.siacs.conversations.entities.Conversation;
|
|
import eu.siacs.conversations.entities.Downloadable;
|
|
import eu.siacs.conversations.entities.DownloadablePlaceholder;
|
|
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.generator.IqGenerator;
|
|
import eu.siacs.conversations.generator.MessageGenerator;
|
|
import eu.siacs.conversations.generator.PresenceGenerator;
|
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
|
import eu.siacs.conversations.parser.IqParser;
|
|
import eu.siacs.conversations.parser.MessageParser;
|
|
import eu.siacs.conversations.parser.PresenceParser;
|
|
import eu.siacs.conversations.persistance.DatabaseBackend;
|
|
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.OnPhoneContactsLoadedListener;
|
|
import eu.siacs.conversations.utils.PRNGFixes;
|
|
import eu.siacs.conversations.utils.PhoneHelper;
|
|
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.OnMessageAcknowledged;
|
|
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
|
|
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
|
|
import eu.siacs.conversations.xmpp.OnStatusChanged;
|
|
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
|
import eu.siacs.conversations.xmpp.PacketReceived;
|
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
|
import eu.siacs.conversations.xmpp.forms.Data;
|
|
import eu.siacs.conversations.xmpp.forms.Field;
|
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
|
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;
|
|
|
|
public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener {
|
|
|
|
public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
|
|
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
|
|
public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground";
|
|
|
|
private ContentObserver contactObserver = new ContentObserver(null) {
|
|
@Override
|
|
public void onChange(boolean selfChange) {
|
|
super.onChange(selfChange);
|
|
Intent intent = new Intent(getApplicationContext(),
|
|
XmppConnectionService.class);
|
|
intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
|
|
startService(intent);
|
|
}
|
|
};
|
|
private final IBinder mBinder = new XmppConnectionBinder();
|
|
public DatabaseBackend databaseBackend;
|
|
public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
|
|
|
|
@Override
|
|
public void onContactStatusChanged(Contact contact, boolean online) {
|
|
Conversation conversation = find(getConversations(), contact);
|
|
if (conversation != null) {
|
|
if (online && contact.getPresences().size() > 1) {
|
|
conversation.endOtrIfNeeded();
|
|
} else {
|
|
conversation.resetOtrSession();
|
|
}
|
|
if (online && (contact.getPresences().size() == 1)) {
|
|
sendUnsentMessages(conversation);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
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 MessageGenerator mMessageGenerator = new MessageGenerator(this);
|
|
private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
|
|
private List<Account> accounts;
|
|
private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
|
|
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
|
|
this);
|
|
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
|
|
this);
|
|
private AvatarService mAvatarService = new AvatarService(this);
|
|
private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
|
|
private OnConversationUpdate mOnConversationUpdate = null;
|
|
private Integer convChangedListenerCount = 0;
|
|
private OnAccountUpdate mOnAccountUpdate = null;
|
|
private OnStatusChanged statusListener = new OnStatusChanged() {
|
|
|
|
@Override
|
|
public void onStatusChanged(Account account) {
|
|
XmppConnection connection = account.getXmppConnection();
|
|
if (mOnAccountUpdate != null) {
|
|
mOnAccountUpdate.onAccountUpdate();
|
|
}
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
for (Conversation conversation : account.pendingConferenceLeaves) {
|
|
leaveMuc(conversation);
|
|
}
|
|
for (Conversation conversation : account.pendingConferenceJoins) {
|
|
joinMuc(conversation);
|
|
}
|
|
mMessageArchiveService.executePendingQueries(account);
|
|
mJingleConnectionManager.cancelInTransmission();
|
|
List<Conversation> conversations = getConversations();
|
|
for (Conversation conversation : conversations) {
|
|
if (conversation.getAccount() == account) {
|
|
conversation.startOtrIfNeeded();
|
|
sendUnsentMessages(conversation);
|
|
}
|
|
}
|
|
if (connection != null && connection.getFeatures().csi()) {
|
|
if (checkListeners()) {
|
|
Log.d(Config.LOGTAG, account.getJid().toBareJid()
|
|
+ " sending csi//inactive");
|
|
connection.sendInactive();
|
|
} else {
|
|
Log.d(Config.LOGTAG, account.getJid().toBareJid()
|
|
+ " sending csi//active");
|
|
connection.sendActive();
|
|
}
|
|
}
|
|
syncDirtyContacts(account);
|
|
scheduleWakeupCall(Config.PING_MAX_INTERVAL, true);
|
|
} else if (account.getStatus() == Account.State.OFFLINE) {
|
|
resetSendingToWaiting(account);
|
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
|
int timeToReconnect = mRandom.nextInt(50) + 10;
|
|
scheduleWakeupCall(timeToReconnect, false);
|
|
}
|
|
} else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
|
|
databaseBackend.updateAccount(account);
|
|
reconnectAccount(account, true);
|
|
} else if ((account.getStatus() != Account.State.CONNECTING)
|
|
&& (account.getStatus() != Account.State.NO_INTERNET)) {
|
|
if (connection != null) {
|
|
int next = connection.getTimeToNextAttempt();
|
|
Log.d(Config.LOGTAG, account.getJid().toBareJid()
|
|
+ ": error connecting account. try again in "
|
|
+ next + "s for the "
|
|
+ (connection.getAttempt() + 1) + " time");
|
|
scheduleWakeupCall((int) (next * 1.2), false);
|
|
}
|
|
}
|
|
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 FileObserver fileObserver = new FileObserver(
|
|
FileBackend.getConversationsImageDirectory()) {
|
|
|
|
@Override
|
|
public void onEvent(int event, String path) {
|
|
if (event == FileObserver.DELETE) {
|
|
markFileDeleted(path.split("\\.")[0]);
|
|
}
|
|
}
|
|
};
|
|
private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
|
|
|
|
@Override
|
|
public void onJinglePacketReceived(Account account, JinglePacket packet) {
|
|
mJingleConnectionManager.deliverPacket(account, packet);
|
|
}
|
|
};
|
|
|
|
private OpenPgpServiceConnection pgpServiceConnection;
|
|
private PgpEngine mPgpEngine = null;
|
|
private Intent pingIntent;
|
|
private PendingIntent pendingPingIntent = null;
|
|
private WakeLock wakeLock;
|
|
private PowerManager pm;
|
|
private OnBindListener mOnBindListener = new OnBindListener() {
|
|
|
|
@Override
|
|
public void onBind(final Account account) {
|
|
account.getRoster().clearPresences();
|
|
account.pendingConferenceJoins.clear();
|
|
account.pendingConferenceLeaves.clear();
|
|
fetchRosterFromServer(account);
|
|
fetchBookmarks(account);
|
|
sendPresencePacket(account,mPresenceGenerator.sendPresence(account));
|
|
connectMultiModeConversations(account);
|
|
updateConversationUi();
|
|
}
|
|
};
|
|
|
|
private 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);
|
|
if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) {
|
|
databaseBackend.updateConversation(conversation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
private LruCache<String, Bitmap> mBitmapCache;
|
|
private IqGenerator mIqGenerator = new IqGenerator(this);
|
|
private Thread mPhoneContactMergerThread;
|
|
|
|
public PgpEngine getPgpEngine() {
|
|
if (pgpServiceConnection.isBound()) {
|
|
if (this.mPgpEngine == null) {
|
|
this.mPgpEngine = new PgpEngine(new OpenPgpApi(
|
|
getApplicationContext(),
|
|
pgpServiceConnection.getService()), this);
|
|
}
|
|
return mPgpEngine;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
public FileBackend getFileBackend() {
|
|
return this.fileBackend;
|
|
}
|
|
|
|
public AvatarService getAvatarService() {
|
|
return this.mAvatarService;
|
|
}
|
|
|
|
public void attachFileToConversation(Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
|
|
final Message message;
|
|
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
|
message = new Message(conversation, "",
|
|
Message.ENCRYPTION_DECRYPTED);
|
|
} else {
|
|
message = new Message(conversation, "",
|
|
conversation.getNextEncryption(forceEncryption()));
|
|
}
|
|
message.setCounterpart(conversation.getNextCounterpart());
|
|
message.setType(Message.TYPE_FILE);
|
|
message.setStatus(Message.STATUS_OFFERED);
|
|
String path = getFileBackend().getOriginalPath(uri);
|
|
if (path!=null) {
|
|
message.setRelativeFilePath(path);
|
|
getFileBackend().updateFileParams(message);
|
|
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
|
getPgpEngine().encrypt(message, callback);
|
|
} else {
|
|
callback.success(message);
|
|
}
|
|
} else {
|
|
new Thread(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);
|
|
}
|
|
}
|
|
}).start();
|
|
|
|
}
|
|
}
|
|
|
|
public void attachImageToConversation(final Conversation conversation,
|
|
final Uri uri, final UiCallback<Message> callback) {
|
|
final Message message;
|
|
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
|
message = new Message(conversation, "",
|
|
Message.ENCRYPTION_DECRYPTED);
|
|
} else {
|
|
message = new Message(conversation, "",
|
|
conversation.getNextEncryption(forceEncryption()));
|
|
}
|
|
message.setCounterpart(conversation.getNextCounterpart());
|
|
message.setType(Message.TYPE_IMAGE);
|
|
message.setStatus(Message.STATUS_OFFERED);
|
|
new Thread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
getFileBackend().copyImageToPrivateStorage(message, uri);
|
|
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
|
getPgpEngine().encrypt(message, callback);
|
|
} else {
|
|
callback.success(message);
|
|
}
|
|
} catch (final FileBackend.FileCopyException e) {
|
|
callback.error(e.getResId(), message);
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
public Conversation find(Bookmark bookmark) {
|
|
return find(bookmark.getAccount(), bookmark.getJid());
|
|
}
|
|
|
|
public Conversation find(final Account account, final Jid jid) {
|
|
return find(getConversations(), account, jid);
|
|
}
|
|
|
|
@Override
|
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
if (intent != null && intent.getAction() != null) {
|
|
if (intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS)) {
|
|
PhoneHelper.loadPhoneContacts(getApplicationContext(), new ArrayList<Bundle>(), this);
|
|
return START_STICKY;
|
|
} else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
|
|
logoutAndSave();
|
|
return START_NOT_STICKY;
|
|
} else if (intent.getAction().equals(ACTION_CLEAR_NOTIFICATION)) {
|
|
mNotificationService.clear();
|
|
} else if (intent.getAction().equals(ACTION_DISABLE_FOREGROUND)) {
|
|
getPreferences().edit().putBoolean("keep_foreground_service",false).commit();
|
|
toggleForegroundService();
|
|
}
|
|
}
|
|
this.wakeLock.acquire();
|
|
|
|
for (Account account : accounts) {
|
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
|
if (!hasInternetConnection()) {
|
|
account.setStatus(Account.State.NO_INTERNET);
|
|
if (statusListener != null) {
|
|
statusListener.onStatusChanged(account);
|
|
}
|
|
} else {
|
|
if (account.getStatus() == Account.State.NO_INTERNET) {
|
|
account.setStatus(Account.State.OFFLINE);
|
|
if (statusListener != null) {
|
|
statusListener.onStatusChanged(account);
|
|
}
|
|
}
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
long lastReceived = account.getXmppConnection()
|
|
.getLastPacketReceived();
|
|
long lastSent = account.getXmppConnection()
|
|
.getLastPingSent();
|
|
if (lastSent - lastReceived >= Config.PING_TIMEOUT * 1000) {
|
|
Log.d(Config.LOGTAG, account.getJid()
|
|
+ ": ping timeout");
|
|
this.reconnectAccount(account, true);
|
|
} else if (SystemClock.elapsedRealtime() - lastReceived >= Config.PING_MIN_INTERVAL * 1000) {
|
|
account.getXmppConnection().sendPing();
|
|
this.scheduleWakeupCall(2, false);
|
|
}
|
|
} else if (account.getStatus() == Account.State.OFFLINE) {
|
|
if (account.getXmppConnection() == null) {
|
|
account.setXmppConnection(this
|
|
.createConnection(account));
|
|
}
|
|
new Thread(account.getXmppConnection()).start();
|
|
} else if ((account.getStatus() == Account.State.CONNECTING)
|
|
&& ((SystemClock.elapsedRealtime() - account
|
|
.getXmppConnection().getLastConnect()) / 1000 >= Config.CONNECT_TIMEOUT)) {
|
|
Log.d(Config.LOGTAG, account.getJid()
|
|
+ ": time out during connect reconnecting");
|
|
reconnectAccount(account, true);
|
|
} else {
|
|
if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
|
|
reconnectAccount(account, true);
|
|
}
|
|
}
|
|
// in any case. reschedule wakup call
|
|
this.scheduleWakeupCall(Config.PING_MAX_INTERVAL, true);
|
|
}
|
|
if (mOnAccountUpdate != null) {
|
|
mOnAccountUpdate.onAccountUpdate();
|
|
}
|
|
}
|
|
}
|
|
/*PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
|
|
if (!pm.isScreenOn()) {
|
|
removeStaleListeners();
|
|
}*/
|
|
if (wakeLock.isHeld()) {
|
|
try {
|
|
wakeLock.release();
|
|
} catch (final RuntimeException ignored) {
|
|
}
|
|
}
|
|
return START_STICKY;
|
|
}
|
|
|
|
public boolean hasInternetConnection() {
|
|
ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
|
|
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
|
return activeNetwork != null && activeNetwork.isConnected();
|
|
}
|
|
|
|
@SuppressLint("TrulyRandom")
|
|
@Override
|
|
public void onCreate() {
|
|
ExceptionHelper.init(getApplicationContext());
|
|
PRNGFixes.apply();
|
|
this.mRandom = new SecureRandom();
|
|
this.mMemorizingTrustManager = new MemorizingTrustManager(
|
|
getApplicationContext());
|
|
|
|
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
|
int cacheSize = maxMemory / 8;
|
|
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
|
|
@Override
|
|
protected int sizeOf(String key, Bitmap bitmap) {
|
|
return bitmap.getByteCount() / 1024;
|
|
}
|
|
};
|
|
|
|
this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
|
|
this.accounts = databaseBackend.getAccounts();
|
|
|
|
for (Account account : this.accounts) {
|
|
account.initOtrEngine(this);
|
|
this.databaseBackend.readRoster(account.getRoster());
|
|
}
|
|
initConversations();
|
|
PhoneHelper.loadPhoneContacts(getApplicationContext(),new ArrayList<Bundle>(), this);
|
|
|
|
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
|
|
this.fileObserver.startWatching();
|
|
this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain");
|
|
this.pgpServiceConnection.bindToService();
|
|
|
|
this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"XmppConnectionService");
|
|
toggleForegroundService();
|
|
}
|
|
|
|
public void toggleForegroundService() {
|
|
if (getPreferences().getBoolean("keep_foreground_service",false)) {
|
|
startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification());
|
|
} else {
|
|
stopForeground(true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTaskRemoved(Intent rootIntent) {
|
|
super.onTaskRemoved(rootIntent);
|
|
if (!getPreferences().getBoolean("keep_foreground_service",false)) {
|
|
this.logoutAndSave();
|
|
}
|
|
}
|
|
|
|
private void logoutAndSave() {
|
|
for (Account account : accounts) {
|
|
databaseBackend.writeRoster(account.getRoster());
|
|
if (account.getXmppConnection() != null) {
|
|
disconnect(account, false);
|
|
}
|
|
}
|
|
Context context = getApplicationContext();
|
|
AlarmManager alarmManager = (AlarmManager) context
|
|
.getSystemService(Context.ALARM_SERVICE);
|
|
Intent intent = new Intent(context, EventReceiver.class);
|
|
alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
|
|
Log.d(Config.LOGTAG, "good bye");
|
|
stopSelf();
|
|
}
|
|
|
|
protected void scheduleWakeupCall(int seconds, boolean ping) {
|
|
long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
|
|
Context context = getApplicationContext();
|
|
AlarmManager alarmManager = (AlarmManager) context
|
|
.getSystemService(Context.ALARM_SERVICE);
|
|
|
|
if (ping) {
|
|
if (this.pingIntent == null) {
|
|
this.pingIntent = new Intent(context, EventReceiver.class);
|
|
this.pingIntent.setAction("ping");
|
|
this.pingIntent.putExtra("time", timeToWake);
|
|
this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
|
|
this.pingIntent, 0);
|
|
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
|
timeToWake, pendingPingIntent);
|
|
} else {
|
|
long scheduledTime = this.pingIntent.getLongExtra("time", 0);
|
|
if (scheduledTime < SystemClock.elapsedRealtime()
|
|
|| (scheduledTime > timeToWake)) {
|
|
this.pingIntent.putExtra("time", timeToWake);
|
|
alarmManager.cancel(this.pendingPingIntent);
|
|
this.pendingPingIntent = PendingIntent.getBroadcast(
|
|
context, 0, this.pingIntent, 0);
|
|
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
|
timeToWake, pendingPingIntent);
|
|
}
|
|
}
|
|
} else {
|
|
Intent intent = new Intent(context, EventReceiver.class);
|
|
intent.setAction("ping_check");
|
|
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
|
|
intent, 0);
|
|
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
|
|
alarmIntent);
|
|
}
|
|
|
|
}
|
|
|
|
public XmppConnection createConnection(final Account account) {
|
|
final SharedPreferences sharedPref = getPreferences();
|
|
account.setResource(sharedPref.getString("resource", "mobile")
|
|
.toLowerCase(Locale.getDefault()));
|
|
final XmppConnection connection = new XmppConnection(account, this);
|
|
connection.setOnMessagePacketReceivedListener(this.mMessageParser);
|
|
connection.setOnStatusChangedListener(this.statusListener);
|
|
connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
|
|
connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
|
|
connection.setOnJinglePacketReceivedListener(this.jingleListener);
|
|
connection.setOnBindListener(this.mOnBindListener);
|
|
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
|
|
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
|
|
return connection;
|
|
}
|
|
|
|
public void sendMessage(final Message message) {
|
|
final Account account = message.getConversation().getAccount();
|
|
account.deactivateGracePeriod();
|
|
final Conversation conv = message.getConversation();
|
|
MessagePacket packet = null;
|
|
boolean saveInDb = true;
|
|
boolean send = false;
|
|
if (account.getStatus() == Account.State.ONLINE
|
|
&& account.getXmppConnection() != null) {
|
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
|
if (message.getCounterpart() != null) {
|
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
if (!conv.hasValidOtrSession()) {
|
|
conv.startOtrSession(message.getCounterpart().getResourcepart(),true);
|
|
message.setStatus(Message.STATUS_WAITING);
|
|
} else if (conv.hasValidOtrSession()
|
|
&& conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
|
|
mJingleConnectionManager
|
|
.createNewConnection(message);
|
|
}
|
|
} else {
|
|
mJingleConnectionManager.createNewConnection(message);
|
|
}
|
|
} else {
|
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
conv.startOtrIfNeeded();
|
|
}
|
|
message.setStatus(Message.STATUS_WAITING);
|
|
}
|
|
} else {
|
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
if (!conv.hasValidOtrSession() && (message.getCounterpart() != null)) {
|
|
conv.startOtrSession(message.getCounterpart().getResourcepart(), true);
|
|
message.setStatus(Message.STATUS_WAITING);
|
|
} else if (conv.hasValidOtrSession()) {
|
|
if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
|
|
packet = mMessageGenerator.generateOtrChat(message);
|
|
send = true;
|
|
} else {
|
|
message.setStatus(Message.STATUS_WAITING);
|
|
conv.startOtrIfNeeded();
|
|
}
|
|
} else {
|
|
message.setStatus(Message.STATUS_WAITING);
|
|
}
|
|
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
|
message.getConversation().endOtrIfNeeded();
|
|
message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
|
@Override
|
|
public void onMessageFound(Message message) {
|
|
markMessage(message,Message.STATUS_SEND_FAILED);
|
|
}
|
|
});
|
|
packet = mMessageGenerator.generatePgpChat(message);
|
|
send = true;
|
|
} else {
|
|
message.getConversation().endOtrIfNeeded();
|
|
message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
|
@Override
|
|
public void onMessageFound(Message message) {
|
|
markMessage(message,Message.STATUS_SEND_FAILED);
|
|
}
|
|
});
|
|
packet = mMessageGenerator.generateChat(message);
|
|
send = true;
|
|
}
|
|
}
|
|
if (!account.getXmppConnection().getFeatures().sm()
|
|
&& conv.getMode() != Conversation.MODE_MULTI) {
|
|
message.setStatus(Message.STATUS_SEND);
|
|
}
|
|
} else {
|
|
message.setStatus(Message.STATUS_WAITING);
|
|
if (message.getType() == Message.TYPE_TEXT) {
|
|
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
|
String pgpBody = message.getEncryptedBody();
|
|
String decryptedBody = message.getBody();
|
|
message.setBody(pgpBody);
|
|
message.setEncryption(Message.ENCRYPTION_PGP);
|
|
databaseBackend.createMessage(message);
|
|
saveInDb = false;
|
|
message.setBody(decryptedBody);
|
|
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
|
} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
if (!conv.hasValidOtrSession()
|
|
&& message.getCounterpart() != null) {
|
|
conv.startOtrSession(message.getCounterpart().getResourcepart(), false);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
conv.add(message);
|
|
if (saveInDb) {
|
|
if (message.getEncryption() == Message.ENCRYPTION_NONE
|
|
|| saveEncryptedMessages()) {
|
|
databaseBackend.createMessage(message);
|
|
}
|
|
}
|
|
if ((send) && (packet != null)) {
|
|
sendMessagePacket(account, packet);
|
|
}
|
|
updateConversationUi();
|
|
}
|
|
|
|
private void sendUnsentMessages(final Conversation conversation) {
|
|
conversation.findWaitingMessages(new Conversation.OnMessageFound() {
|
|
|
|
@Override
|
|
public void onMessageFound(Message message) {
|
|
resendMessage(message);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void resendMessage(final Message message) {
|
|
Account account = message.getConversation().getAccount();
|
|
MessagePacket packet = null;
|
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
Presences presences = message.getConversation().getContact()
|
|
.getPresences();
|
|
if (!message.getConversation().hasValidOtrSession()) {
|
|
if ((message.getCounterpart() != null)
|
|
&& (presences.has(message.getCounterpart().getResourcepart()))) {
|
|
message.getConversation().startOtrSession(message.getCounterpart().getResourcepart(), true);
|
|
} else {
|
|
if (presences.size() == 1) {
|
|
String presence = presences.asStringArray()[0];
|
|
message.getConversation().startOtrSession(presence, true);
|
|
}
|
|
}
|
|
} else {
|
|
if (message.getConversation().getOtrSession()
|
|
.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
|
try {
|
|
message.setCounterpart(Jid.fromSessionID(message.getConversation().getOtrSession().getSessionID()));
|
|
if (message.getType() == Message.TYPE_TEXT) {
|
|
packet = mMessageGenerator.generateOtrChat(message,
|
|
true);
|
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
|
mJingleConnectionManager.createNewConnection(message);
|
|
}
|
|
} catch (final InvalidJidException ignored) {
|
|
|
|
}
|
|
}
|
|
}
|
|
} else if (message.getType() == Message.TYPE_TEXT) {
|
|
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
|
|
packet = mMessageGenerator.generateChat(message, true);
|
|
} else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
|
|| (message.getEncryption() == Message.ENCRYPTION_PGP)) {
|
|
packet = mMessageGenerator.generatePgpChat(message, true);
|
|
}
|
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
|
Contact contact = message.getConversation().getContact();
|
|
Presences presences = contact.getPresences();
|
|
if ((message.getCounterpart() != null)
|
|
&& (presences.has(message.getCounterpart().getResourcepart()))) {
|
|
markMessage(message, Message.STATUS_OFFERED);
|
|
mJingleConnectionManager.createNewConnection(message);
|
|
} else {
|
|
if (presences.size() == 1) {
|
|
String presence = presences.asStringArray()[0];
|
|
try {
|
|
message.setCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence));
|
|
} catch (InvalidJidException e) {
|
|
return;
|
|
}
|
|
markMessage(message, Message.STATUS_OFFERED);
|
|
mJingleConnectionManager.createNewConnection(message);
|
|
}
|
|
}
|
|
}
|
|
if (packet != null) {
|
|
if (!account.getXmppConnection().getFeatures().sm()
|
|
&& message.getConversation().getMode() != Conversation.MODE_MULTI) {
|
|
markMessage(message, Message.STATUS_SEND);
|
|
} else {
|
|
markMessage(message, Message.STATUS_UNSEND);
|
|
}
|
|
sendMessagePacket(account, packet);
|
|
}
|
|
}
|
|
|
|
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()
|
|
+ ": fetching roster version " + account.getRosterVersion());
|
|
} else {
|
|
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
|
|
}
|
|
iqPacket.query("jabber:iq:roster").setAttribute("ver",
|
|
account.getRosterVersion());
|
|
account.getXmppConnection().sendIqPacket(iqPacket,
|
|
new OnIqPacketReceived() {
|
|
|
|
@Override
|
|
public void onIqPacketReceived(final Account account,
|
|
final IqPacket packet) {
|
|
final Element query = packet.findChild("query");
|
|
if (query != null) {
|
|
account.getRoster().markAllAsNotInRoster();
|
|
mIqParser.rosterItems(account, query);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public void fetchBookmarks(final Account account) {
|
|
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
|
|
final Element query = iqPacket.query("jabber:iq:private");
|
|
query.addChild("storage", "storage:bookmarks");
|
|
final PacketReceived callback = new OnIqPacketReceived() {
|
|
|
|
@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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
account.setBookmarks(bookmarks);
|
|
}
|
|
};
|
|
sendIqPacket(account, iqPacket, callback);
|
|
}
|
|
|
|
public void pushBookmarks(Account account) {
|
|
IqPacket iqPacket = new IqPacket(IqPacket.TYPE_SET);
|
|
Element query = iqPacket.query("jabber:iq:private");
|
|
Element storage = query.addChild("storage", "storage:bookmarks");
|
|
for (Bookmark bookmark : account.getBookmarks()) {
|
|
storage.addChild(bookmark);
|
|
}
|
|
sendIqPacket(account, iqPacket, null);
|
|
}
|
|
|
|
public void onPhoneContactsLoaded(final List<Bundle> phoneContacts) {
|
|
if (mPhoneContactMergerThread != null) {
|
|
mPhoneContactMergerThread.interrupt();
|
|
}
|
|
mPhoneContactMergerThread = new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Log.d(Config.LOGTAG,"start merging phone contacts with roster");
|
|
for (Account account : accounts) {
|
|
account.getRoster().clearSystemAccounts();
|
|
for (Bundle phoneContact : phoneContacts) {
|
|
if (Thread.interrupted()) {
|
|
Log.d(Config.LOGTAG,"interrupted merging phone contacts");
|
|
return;
|
|
}
|
|
Jid jid;
|
|
try {
|
|
jid = Jid.fromString(phoneContact.getString("jid"));
|
|
} catch (final InvalidJidException e) {
|
|
continue;
|
|
}
|
|
final Contact contact = account.getRoster().getContact(jid);
|
|
String systemAccount = phoneContact.getInt("phoneid")
|
|
+ "#"
|
|
+ phoneContact.getString("lookup");
|
|
contact.setSystemAccount(systemAccount);
|
|
contact.setPhotoUri(phoneContact.getString("photouri"));
|
|
getAvatarService().clear(contact);
|
|
contact.setSystemName(phoneContact.getString("displayname"));
|
|
}
|
|
}
|
|
Log.d(Config.LOGTAG,"finished merging phone contacts");
|
|
updateAccountUi();
|
|
}
|
|
});
|
|
mPhoneContactMergerThread.start();
|
|
}
|
|
|
|
private void initConversations() {
|
|
synchronized (this.conversations) {
|
|
final Map<String, Account> accountLookupTable = new Hashtable<>();
|
|
for (Account account : this.accounts) {
|
|
accountLookupTable.put(account.getUuid(), account);
|
|
}
|
|
this.conversations.addAll(databaseBackend.getConversations(Conversation.STATUS_AVAILABLE));
|
|
for (Conversation conversation : this.conversations) {
|
|
Account account = accountLookupTable.get(conversation.getAccountUuid());
|
|
conversation.setAccount(account);
|
|
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
|
|
checkDeletedFiles(conversation);
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<Conversation> getConversations() {
|
|
return this.conversations;
|
|
}
|
|
|
|
private void checkDeletedFiles(Conversation conversation) {
|
|
conversation.findMessagesWithFiles(new Conversation.OnMessageFound() {
|
|
|
|
@Override
|
|
public void onMessageFound(Message message) {
|
|
if (!getFileBackend().isFileAvailable(message)) {
|
|
message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void markFileDeleted(String uuid) {
|
|
for (Conversation conversation : getConversations()) {
|
|
Message message = conversation.findMessageWithFileAndUuid(uuid);
|
|
if (message != null) {
|
|
if (!getFileBackend().isFileAvailable(message)) {
|
|
message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
|
|
updateConversationUi();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void populateWithOrderedConversations(final List<Conversation> list) {
|
|
populateWithOrderedConversations(list, true);
|
|
}
|
|
|
|
public void populateWithOrderedConversations(final List<Conversation> list, boolean includeConferences) {
|
|
list.clear();
|
|
if (includeConferences) {
|
|
list.addAll(getConversations());
|
|
} else {
|
|
for (Conversation conversation : getConversations()) {
|
|
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
list.add(conversation);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) {
|
|
Log.d(Config.LOGTAG,"load more messages for "+conversation.getName() + " prior to "+MessageGenerator.getTimestamp(timestamp));
|
|
if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) {
|
|
return;
|
|
}
|
|
new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
final Account account = conversation.getAccount();
|
|
List<Message> messages = databaseBackend.getMessages(conversation, 50,timestamp);
|
|
if (messages.size() > 0) {
|
|
conversation.addAll(0, messages);
|
|
callback.onMoreMessagesLoaded(messages.size(), conversation);
|
|
} else if (account.getStatus() == Account.State.ONLINE && account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) {
|
|
MessageArchiveService.Query query = getMessageArchiveService().query(conversation,0,timestamp - 1);
|
|
if (query != null) {
|
|
query.setCallback(callback);
|
|
}
|
|
callback.informUser(R.string.fetching_history_from_server);
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
public interface OnMoreMessagesLoaded {
|
|
public void onMoreMessagesLoaded(int count,Conversation conversation);
|
|
public void informUser(int r);
|
|
}
|
|
|
|
public List<Account> getAccounts() {
|
|
return this.accounts;
|
|
}
|
|
|
|
public Conversation find(final Iterable<Conversation> haystack, final Contact contact) {
|
|
for (final Conversation conversation : haystack) {
|
|
if (conversation.getContact() == contact) {
|
|
return conversation;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Conversation find(final Iterable<Conversation> haystack, final Account account, final Jid jid) {
|
|
if (jid == null ) {
|
|
return null;
|
|
}
|
|
for (final Conversation conversation : haystack) {
|
|
if ((account == null || conversation.getAccount() == account)
|
|
&& (conversation.getJid().toBareJid().equals(jid.toBareJid()))) {
|
|
return conversation;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc) {
|
|
return this.findOrCreateConversation(account,jid,muc,null);
|
|
}
|
|
|
|
public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc, final MessageArchiveService.Query query) {
|
|
synchronized (this.conversations) {
|
|
Conversation conversation = find(account, jid);
|
|
if (conversation != null) {
|
|
return conversation;
|
|
}
|
|
conversation = databaseBackend.findConversation(account, jid);
|
|
if (conversation != null) {
|
|
conversation.setStatus(Conversation.STATUS_AVAILABLE);
|
|
conversation.setAccount(account);
|
|
if (muc) {
|
|
conversation.setMode(Conversation.MODE_MULTI);
|
|
} else {
|
|
conversation.setMode(Conversation.MODE_SINGLE);
|
|
}
|
|
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
|
|
this.databaseBackend.updateConversation(conversation);
|
|
} else {
|
|
String conversationName;
|
|
Contact contact = account.getRoster().getContact(jid);
|
|
if (contact != null) {
|
|
conversationName = contact.getDisplayName();
|
|
} else {
|
|
conversationName = jid.getLocalpart();
|
|
}
|
|
if (muc) {
|
|
conversation = new Conversation(conversationName, account, jid,
|
|
Conversation.MODE_MULTI);
|
|
} else {
|
|
conversation = new Conversation(conversationName, account, jid,
|
|
Conversation.MODE_SINGLE);
|
|
}
|
|
this.databaseBackend.createConversation(conversation);
|
|
}
|
|
if (query == null) {
|
|
this.mMessageArchiveService.query(conversation);
|
|
} else {
|
|
if (query.getConversation() == null) {
|
|
this.mMessageArchiveService.query(conversation,query.getStart());
|
|
}
|
|
}
|
|
this.conversations.add(conversation);
|
|
updateConversationUi();
|
|
return conversation;
|
|
}
|
|
}
|
|
|
|
public void archiveConversation(Conversation conversation) {
|
|
synchronized (this.conversations) {
|
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
|
|
Bookmark bookmark = conversation.getBookmark();
|
|
if (bookmark != null && bookmark.autojoin()) {
|
|
bookmark.setAutojoin(false);
|
|
pushBookmarks(bookmark.getAccount());
|
|
}
|
|
}
|
|
leaveMuc(conversation);
|
|
} else {
|
|
conversation.endOtrIfNeeded();
|
|
}
|
|
this.databaseBackend.updateConversation(conversation);
|
|
this.conversations.remove(conversation);
|
|
updateConversationUi();
|
|
}
|
|
}
|
|
|
|
public void createAccount(Account account) {
|
|
account.initOtrEngine(this);
|
|
databaseBackend.createAccount(account);
|
|
this.accounts.add(account);
|
|
this.reconnectAccount(account, false);
|
|
updateAccountUi();
|
|
}
|
|
|
|
public void updateAccount(Account account) {
|
|
this.statusListener.onStatusChanged(account);
|
|
databaseBackend.updateAccount(account);
|
|
reconnectAccount(account, false);
|
|
updateAccountUi();
|
|
getNotificationService().updateErrorNotification();
|
|
}
|
|
|
|
public void deleteAccount(Account account) {
|
|
synchronized (this.conversations) {
|
|
for (Conversation conversation : conversations) {
|
|
if (conversation.getAccount() == account) {
|
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
leaveMuc(conversation);
|
|
} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
conversation.endOtrIfNeeded();
|
|
}
|
|
conversations.remove(conversation);
|
|
}
|
|
}
|
|
if (account.getXmppConnection() != null) {
|
|
this.disconnect(account, true);
|
|
}
|
|
databaseBackend.deleteAccount(account);
|
|
this.accounts.remove(account);
|
|
updateAccountUi();
|
|
getNotificationService().updateErrorNotification();
|
|
}
|
|
}
|
|
|
|
public void setOnConversationListChangedListener(OnConversationUpdate listener) {
|
|
synchronized (this) {
|
|
if (checkListeners()) {
|
|
switchToForeground();
|
|
}
|
|
this.mOnConversationUpdate = listener;
|
|
this.mNotificationService.setIsInForeground(true);
|
|
if (this.convChangedListenerCount < 2) {
|
|
this.convChangedListenerCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeOnConversationListChangedListener() {
|
|
synchronized (this) {
|
|
this.convChangedListenerCount--;
|
|
if (this.convChangedListenerCount <= 0) {
|
|
this.convChangedListenerCount = 0;
|
|
this.mOnConversationUpdate = null;
|
|
this.mNotificationService.setIsInForeground(false);
|
|
if (checkListeners()) {
|
|
switchToBackground();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setOnAccountListChangedListener(OnAccountUpdate listener) {
|
|
synchronized (this) {
|
|
if (checkListeners()) {
|
|
switchToForeground();
|
|
}
|
|
this.mOnAccountUpdate = listener;
|
|
if (this.accountChangedListenerCount < 2) {
|
|
this.accountChangedListenerCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeOnAccountListChangedListener() {
|
|
synchronized (this) {
|
|
this.accountChangedListenerCount--;
|
|
if (this.accountChangedListenerCount <= 0) {
|
|
this.mOnAccountUpdate = null;
|
|
this.accountChangedListenerCount = 0;
|
|
if (checkListeners()) {
|
|
switchToBackground();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setOnRosterUpdateListener(final OnRosterUpdate listener) {
|
|
synchronized (this) {
|
|
if (checkListeners()) {
|
|
switchToForeground();
|
|
}
|
|
this.mOnRosterUpdate = listener;
|
|
if (this.rosterChangedListenerCount < 2) {
|
|
this.rosterChangedListenerCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeOnRosterUpdateListener() {
|
|
synchronized (this) {
|
|
this.rosterChangedListenerCount--;
|
|
if (this.rosterChangedListenerCount <= 0) {
|
|
this.rosterChangedListenerCount = 0;
|
|
this.mOnRosterUpdate = null;
|
|
if (checkListeners()) {
|
|
switchToBackground();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) {
|
|
synchronized (this) {
|
|
if (checkListeners()) {
|
|
switchToForeground();
|
|
}
|
|
this.mOnUpdateBlocklist = listener;
|
|
if (this.updateBlocklistListenerCount < 2) {
|
|
this.updateBlocklistListenerCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeOnUpdateBlocklistListener() {
|
|
synchronized (this) {
|
|
this.updateBlocklistListenerCount--;
|
|
if (this.updateBlocklistListenerCount <= 0) {
|
|
this.updateBlocklistListenerCount = 0;
|
|
this.mOnUpdateBlocklist = null;
|
|
if (checkListeners()) {
|
|
switchToBackground();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
|
|
synchronized (this) {
|
|
if (checkListeners()) {
|
|
switchToForeground();
|
|
}
|
|
this.mOnMucRosterUpdate = listener;
|
|
if (this.mucRosterChangedListenerCount < 2) {
|
|
this.mucRosterChangedListenerCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeOnMucRosterUpdateListener() {
|
|
synchronized (this) {
|
|
this.mucRosterChangedListenerCount--;
|
|
if (this.mucRosterChangedListenerCount <= 0) {
|
|
this.mucRosterChangedListenerCount = 0;
|
|
this.mOnMucRosterUpdate = null;
|
|
if (checkListeners()) {
|
|
switchToBackground();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean checkListeners() {
|
|
return (this.mOnAccountUpdate == null
|
|
&& this.mOnConversationUpdate == null && this.mOnRosterUpdate == null);
|
|
}
|
|
|
|
private void switchToForeground() {
|
|
for (Account account : getAccounts()) {
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
XmppConnection connection = account.getXmppConnection();
|
|
if (connection != null && connection.getFeatures().csi()) {
|
|
connection.sendActive();
|
|
}
|
|
}
|
|
}
|
|
Log.d(Config.LOGTAG, "app switched into foreground");
|
|
}
|
|
|
|
private void switchToBackground() {
|
|
for (Account account : getAccounts()) {
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
XmppConnection connection = account.getXmppConnection();
|
|
if (connection != null && connection.getFeatures().csi()) {
|
|
connection.sendInactive();
|
|
}
|
|
}
|
|
}
|
|
this.mNotificationService.setIsInForeground(false);
|
|
Log.d(Config.LOGTAG, "app switched into background");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void joinMuc(Conversation conversation) {
|
|
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
|
|
}
|
|
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());
|
|
}
|
|
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);
|
|
if (!joinJid.equals(conversation.getJid())) {
|
|
conversation.setContactJid(joinJid);
|
|
databaseBackend.updateConversation(conversation);
|
|
}
|
|
} else {
|
|
account.pendingConferenceJoins.add(conversation);
|
|
}
|
|
}
|
|
|
|
public void providePasswordForMuc(Conversation conversation, String password) {
|
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
conversation.getMucOptions().setPassword(password);
|
|
if (conversation.getBookmark() != null) {
|
|
conversation.getBookmark().setAutojoin(true);
|
|
pushBookmarks(conversation.getAccount());
|
|
}
|
|
databaseBackend.updateConversation(conversation);
|
|
joinMuc(conversation);
|
|
}
|
|
}
|
|
|
|
public void renameInMuc(final Conversation conversation, final String nick, final UiCallback<Conversation> callback) {
|
|
final MucOptions options = conversation.getMucOptions();
|
|
final Jid joinJid = options.createJoinJid(nick);
|
|
if (options.online()) {
|
|
Account account = conversation.getAccount();
|
|
options.setOnRenameListener(new OnRenameListener() {
|
|
|
|
@Override
|
|
public void onSuccess() {
|
|
conversation.setContactJid(joinJid);
|
|
databaseBackend.updateConversation(conversation);
|
|
Bookmark bookmark = conversation.getBookmark();
|
|
if (bookmark != null) {
|
|
bookmark.setNick(nick);
|
|
pushBookmarks(bookmark.getAccount());
|
|
}
|
|
callback.success(conversation);
|
|
}
|
|
|
|
@Override
|
|
public void onFailure() {
|
|
callback.error(R.string.nick_in_use, conversation);
|
|
}
|
|
});
|
|
|
|
PresencePacket packet = new PresencePacket();
|
|
packet.setTo(joinJid);
|
|
packet.setFrom(conversation.getAccount().getJid());
|
|
|
|
String sig = account.getPgpSignature();
|
|
if (sig != null) {
|
|
packet.addChild("status").setContent("online");
|
|
packet.addChild("x", "jabber:x:signed").setContent(sig);
|
|
}
|
|
sendPresencePacket(account, packet);
|
|
} else {
|
|
conversation.setContactJid(joinJid);
|
|
databaseBackend.updateConversation(conversation);
|
|
if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
|
|
Bookmark bookmark = conversation.getBookmark();
|
|
if (bookmark != null) {
|
|
bookmark.setNick(nick);
|
|
pushBookmarks(bookmark.getAccount());
|
|
}
|
|
joinMuc(conversation);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void leaveMuc(Conversation conversation) {
|
|
Account account = conversation.getAccount();
|
|
account.pendingConferenceJoins.remove(conversation);
|
|
account.pendingConferenceLeaves.remove(conversation);
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
PresencePacket packet = new PresencePacket();
|
|
packet.setTo(conversation.getJid());
|
|
packet.setFrom(conversation.getAccount().getJid());
|
|
packet.setAttribute("type", "unavailable");
|
|
sendPresencePacket(conversation.getAccount(), packet);
|
|
conversation.getMucOptions().setOffline();
|
|
conversation.deregisterWithBookmark();
|
|
Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
|
|
+ ": leaving muc " + conversation.getJid());
|
|
} else {
|
|
account.pendingConferenceLeaves.add(conversation);
|
|
}
|
|
}
|
|
|
|
private String findConferenceServer(final Account account) {
|
|
String server;
|
|
if (account.getXmppConnection() != null) {
|
|
server = account.getXmppConnection().getMucServer();
|
|
if (server != null) {
|
|
return server;
|
|
}
|
|
}
|
|
for(Account other : getAccounts()) {
|
|
if (other != account && other.getXmppConnection() != null) {
|
|
server = other.getXmppConnection().getMucServer();
|
|
if (server != null) {
|
|
return server;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
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());
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
try {
|
|
String server = findConferenceServer(account);
|
|
if (server == null) {
|
|
if (callback != null) {
|
|
callback.error(R.string.no_conference_server_found,null);
|
|
}
|
|
return;
|
|
}
|
|
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() {
|
|
@Override
|
|
public void onPushSucceeded() {
|
|
for(Jid invite : jids) {
|
|
invite(conversation,invite);
|
|
}
|
|
if (callback != null) {
|
|
callback.success(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);
|
|
}
|
|
}
|
|
} else {
|
|
if (callback != null) {
|
|
callback.error(R.string.not_connected_try_again,null);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void pushConferenceConfiguration(final Conversation conversation,final Bundle options, final OnConferenceOptionsPushed callback) {
|
|
IqPacket request = new IqPacket(IqPacket.TYPE_GET);
|
|
request.setTo(conversation.getJid().toBareJid());
|
|
request.query("http://jabber.org/protocol/muc#owner");
|
|
sendIqPacket(conversation.getAccount(),request,new OnIqPacketReceived() {
|
|
@Override
|
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
|
if (packet.getType() != IqPacket.TYPE_ERROR) {
|
|
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()));
|
|
}
|
|
}
|
|
data.submit();
|
|
IqPacket set = new IqPacket(IqPacket.TYPE_SET);
|
|
set.setTo(conversation.getJid().toBareJid());
|
|
set.query("http://jabber.org/protocol/muc#owner").addChild(data);
|
|
sendIqPacket(account, set, new OnIqPacketReceived() {
|
|
@Override
|
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
|
if (packet.getType() == IqPacket.TYPE_RESULT) {
|
|
if (callback != null) {
|
|
callback.onPushSucceeded();
|
|
}
|
|
} else {
|
|
if (callback != null) {
|
|
callback.onPushFailed();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
if (callback != null) {
|
|
callback.onPushFailed();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public void disconnect(Account account, boolean force) {
|
|
if ((account.getStatus() == Account.State.ONLINE)
|
|
|| (account.getStatus() == Account.State.DISABLED)) {
|
|
if (!force) {
|
|
List<Conversation> conversations = getConversations();
|
|
for (Conversation conversation : conversations) {
|
|
if (conversation.getAccount() == account) {
|
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
leaveMuc(conversation);
|
|
} else {
|
|
if (conversation.endOtrIfNeeded()) {
|
|
Log.d(Config.LOGTAG, account.getJid().toBareJid()
|
|
+ ": ended otr session with "
|
|
+ conversation.getJid());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
account.getXmppConnection().disconnect(force);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
return mBinder;
|
|
}
|
|
|
|
public void updateMessage(Message message) {
|
|
databaseBackend.updateMessage(message);
|
|
updateConversationUi();
|
|
}
|
|
|
|
protected void syncDirtyContacts(Account account) {
|
|
for (Contact contact : account.getRoster().getContacts()) {
|
|
if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
|
|
pushContactToServer(contact);
|
|
}
|
|
if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
|
|
deleteContactOnServer(contact);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void createContact(Contact contact) {
|
|
SharedPreferences sharedPref = getPreferences();
|
|
boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
|
|
if (autoGrant) {
|
|
contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
|
|
contact.setOption(Contact.Options.ASKING);
|
|
}
|
|
pushContactToServer(contact);
|
|
}
|
|
|
|
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());
|
|
conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
|
|
|
@Override
|
|
public void onMessageFound(Message message) {
|
|
SessionID id = otrSession.getSessionID();
|
|
try {
|
|
message.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID()));
|
|
} catch (InvalidJidException e) {
|
|
return;
|
|
}
|
|
if (message.getType() == Message.TYPE_TEXT) {
|
|
MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true);
|
|
if (outPacket != null) {
|
|
message.setStatus(Message.STATUS_SEND);
|
|
databaseBackend.updateMessage(message);
|
|
sendMessagePacket(account, outPacket);
|
|
}
|
|
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
|
mJingleConnectionManager.createNewConnection(message);
|
|
}
|
|
updateConversationUi();
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean renewSymmetricKey(Conversation conversation) {
|
|
Account account = conversation.getAccount();
|
|
byte[] symmetricKey = new byte[32];
|
|
this.mRandom.nextBytes(symmetricKey);
|
|
Session otrSession = conversation.getOtrSession();
|
|
if (otrSession != null) {
|
|
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");
|
|
packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
|
|
+ otrSession.getSessionID().getUserID());
|
|
try {
|
|
packet.setBody(otrSession
|
|
.transformSending(CryptoHelper.FILETRANSFER
|
|
+ CryptoHelper.bytesToHex(symmetricKey)));
|
|
sendMessagePacket(account, packet);
|
|
conversation.setSymmetricKey(symmetricKey);
|
|
return true;
|
|
} catch (OtrException e) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void pushContactToServer(Contact contact) {
|
|
contact.resetOption(Contact.Options.DIRTY_DELETE);
|
|
contact.setOption(Contact.Options.DIRTY_PUSH);
|
|
Account account = contact.getAccount();
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
boolean ask = contact.getOption(Contact.Options.ASKING);
|
|
boolean sendUpdates = contact
|
|
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
|
|
&& contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
|
|
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
|
iq.query("jabber:iq:roster").addChild(contact.asElement());
|
|
account.getXmppConnection().sendIqPacket(iq, null);
|
|
if (sendUpdates) {
|
|
sendPresencePacket(account,
|
|
mPresenceGenerator.sendPresenceUpdatesTo(contact));
|
|
}
|
|
if (ask) {
|
|
sendPresencePacket(account,
|
|
mPresenceGenerator.requestPresenceUpdatesFrom(contact));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void publishAvatar(Account account, 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;
|
|
}
|
|
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) {
|
|
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())) {
|
|
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) {
|
|
IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar);
|
|
sendIqPacket(account, packet, new OnIqPacketReceived() {
|
|
|
|
@Override
|
|
public void onIqPacketReceived(Account account, IqPacket result) {
|
|
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.getFilename());
|
|
getAvatarService().clear(contact);
|
|
updateConversationUi();
|
|
updateRosterUi();
|
|
}
|
|
if (callback != null) {
|
|
callback.success(avatar);
|
|
}
|
|
Log.d(Config.LOGTAG, account.getJid().toBareJid()
|
|
+ ": succesfully fetched 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);
|
|
}
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
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 {
|
|
fetchAvatar(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);
|
|
contact.setOption(Contact.Options.DIRTY_DELETE);
|
|
Account account = contact.getAccount();
|
|
if (account.getStatus() == Account.State.ONLINE) {
|
|
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
|
Element item = iq.query("jabber:iq:roster").addChild("item");
|
|
item.setAttribute("jid", contact.getJid().toString());
|
|
item.setAttribute("subscription", "remove");
|
|
account.getXmppConnection().sendIqPacket(iq, null);
|
|
}
|
|
}
|
|
|
|
public void updateConversation(Conversation conversation) {
|
|
this.databaseBackend.updateConversation(conversation);
|
|
}
|
|
|
|
public void reconnectAccount(final Account account, final boolean force) {
|
|
new Thread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (account.getXmppConnection() != null) {
|
|
disconnect(account, force);
|
|
}
|
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
|
if (account.getXmppConnection() == null) {
|
|
account.setXmppConnection(createConnection(account));
|
|
}
|
|
Thread thread = new Thread(account.getXmppConnection());
|
|
thread.start();
|
|
scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2),
|
|
false);
|
|
} else {
|
|
account.getRoster().clearPresences();
|
|
account.setXmppConnection(null);
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
public void invite(Conversation conversation, Jid contact) {
|
|
MessagePacket packet = mMessageGenerator.invite(conversation, contact);
|
|
sendMessagePacket(conversation.getAccount(), packet);
|
|
}
|
|
|
|
public void resetSendingToWaiting(Account account) {
|
|
for (Conversation conversation : getConversations()) {
|
|
if (conversation.getAccount() == account) {
|
|
conversation.findUnsentTextMessages(new Conversation.OnMessageFound() {
|
|
|
|
@Override
|
|
public void onMessageFound(Message message) {
|
|
markMessage(message, Message.STATUS_WAITING);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean markMessage(final Account account, final Jid recipient, final String uuid,
|
|
final int status) {
|
|
if (uuid == null) {
|
|
return false;
|
|
} else {
|
|
for (Conversation conversation : getConversations()) {
|
|
if (conversation.getJid().equals(recipient)
|
|
&& conversation.getAccount().equals(account)) {
|
|
return markMessage(conversation, uuid, status);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public boolean markMessage(Conversation conversation, String uuid,
|
|
int status) {
|
|
if (uuid == null) {
|
|
return false;
|
|
} else {
|
|
Message message = conversation.findSentMessageWithUuid(uuid);
|
|
if (message!=null) {
|
|
markMessage(message,status);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void markMessage(Message message, int status) {
|
|
if (status == Message.STATUS_SEND_FAILED
|
|
&& (message.getStatus() == Message.STATUS_SEND_RECEIVED || message
|
|
.getStatus() == Message.STATUS_SEND_DISPLAYED)) {
|
|
return;
|
|
}
|
|
message.setStatus(status);
|
|
databaseBackend.updateMessage(message);
|
|
updateConversationUi();
|
|
}
|
|
|
|
public SharedPreferences getPreferences() {
|
|
return PreferenceManager
|
|
.getDefaultSharedPreferences(getApplicationContext());
|
|
}
|
|
|
|
public boolean forceEncryption() {
|
|
return getPreferences().getBoolean("force_encryption", false);
|
|
}
|
|
|
|
public boolean confirmMessages() {
|
|
return getPreferences().getBoolean("confirm_messages", true);
|
|
}
|
|
|
|
public boolean saveEncryptedMessages() {
|
|
return !getPreferences().getBoolean("dont_save_encrypted", false);
|
|
}
|
|
|
|
public boolean indicateReceived() {
|
|
return getPreferences().getBoolean("indicate_received", false);
|
|
}
|
|
|
|
public void updateConversationUi() {
|
|
if (mOnConversationUpdate != null) {
|
|
mOnConversationUpdate.onConversationUpdate();
|
|
}
|
|
}
|
|
|
|
public void updateAccountUi() {
|
|
if (mOnAccountUpdate != null) {
|
|
mOnAccountUpdate.onAccountUpdate();
|
|
}
|
|
}
|
|
|
|
public void updateRosterUi() {
|
|
if (mOnRosterUpdate != null) {
|
|
mOnRosterUpdate.onRosterUpdate();
|
|
}
|
|
}
|
|
|
|
public void updateBlocklistUi(final OnUpdateBlocklist.Status status) {
|
|
if (mOnUpdateBlocklist != null) {
|
|
mOnUpdateBlocklist.OnUpdateBlocklist(status);
|
|
}
|
|
}
|
|
|
|
public void updateMucRosterUi() {
|
|
if (mOnMucRosterUpdate != null) {
|
|
mOnMucRosterUpdate.onMucRosterUpdate();
|
|
}
|
|
}
|
|
|
|
public Account findAccountByJid(final Jid accountJid) {
|
|
for (Account account : this.accounts) {
|
|
if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {
|
|
return account;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Conversation findConversationByUuid(String uuid) {
|
|
for (Conversation conversation : getConversations()) {
|
|
if (conversation.getUuid().equals(uuid)) {
|
|
return conversation;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void markRead(Conversation conversation, boolean calledByUi) {
|
|
mNotificationService.clear(conversation);
|
|
final Message markable = conversation.getLatestMarkableMessage();
|
|
conversation.markRead();
|
|
if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null && calledByUi) {
|
|
Log.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());
|
|
this.sendMessagePacket(conversation.getAccount(),packet);
|
|
}
|
|
if (!calledByUi) {
|
|
updateConversationUi();
|
|
}
|
|
}
|
|
|
|
public SecureRandom getRNG() {
|
|
return this.mRandom;
|
|
}
|
|
|
|
public MemorizingTrustManager getMemorizingTrustManager() {
|
|
return this.mMemorizingTrustManager;
|
|
}
|
|
|
|
public PowerManager getPowerManager() {
|
|
return this.pm;
|
|
}
|
|
|
|
public LruCache<String, Bitmap> getBitmapCache() {
|
|
return this.mBitmapCache;
|
|
}
|
|
|
|
public void syncRosterToDisk(final Account account) {
|
|
new Thread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
databaseBackend.writeRoster(account.getRoster());
|
|
}
|
|
}).start();
|
|
|
|
}
|
|
|
|
public List<String> getKnownHosts() {
|
|
List<String> hosts = new ArrayList<>();
|
|
for (Account account : getAccounts()) {
|
|
if (!hosts.contains(account.getServer().toString())) {
|
|
hosts.add(account.getServer().toString());
|
|
}
|
|
for (Contact contact : account.getRoster().getContacts()) {
|
|
if (contact.showInRoster()) {
|
|
final String server = contact.getServer().toString();
|
|
if (server != null && !hosts.contains(server)) {
|
|
hosts.add(server);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return hosts;
|
|
}
|
|
|
|
public List<String> getKnownConferenceHosts() {
|
|
ArrayList<String> mucServers = new ArrayList<>();
|
|
for (Account account : accounts) {
|
|
if (account.getXmppConnection() != null) {
|
|
String server = account.getXmppConnection().getMucServer();
|
|
if (server != null && !mucServers.contains(server)) {
|
|
mucServers.add(server);
|
|
}
|
|
}
|
|
}
|
|
return mucServers;
|
|
}
|
|
|
|
public void sendMessagePacket(Account account, MessagePacket packet) {
|
|
XmppConnection connection = account.getXmppConnection();
|
|
if (connection != null) {
|
|
connection.sendMessagePacket(packet);
|
|
}
|
|
}
|
|
|
|
public void sendPresencePacket(Account account, PresencePacket packet) {
|
|
XmppConnection connection = account.getXmppConnection();
|
|
if (connection != null) {
|
|
connection.sendPresencePacket(packet);
|
|
}
|
|
}
|
|
|
|
public void sendIqPacket(final Account account, final IqPacket packet, final PacketReceived callback) {
|
|
final XmppConnection connection = account.getXmppConnection();
|
|
if (connection != null) {
|
|
connection.sendIqPacket(packet, callback);
|
|
}
|
|
}
|
|
|
|
public MessageGenerator getMessageGenerator() {
|
|
return this.mMessageGenerator;
|
|
}
|
|
|
|
public PresenceGenerator getPresenceGenerator() {
|
|
return this.mPresenceGenerator;
|
|
}
|
|
|
|
public IqGenerator getIqGenerator() {
|
|
return this.mIqGenerator;
|
|
}
|
|
|
|
public IqParser getIqParser() { return this.mIqParser; }
|
|
|
|
public JingleConnectionManager getJingleConnectionManager() {
|
|
return this.mJingleConnectionManager;
|
|
}
|
|
|
|
public MessageArchiveService getMessageArchiveService() {
|
|
return this.mMessageArchiveService;
|
|
}
|
|
|
|
public List<Contact> findContacts(Jid jid) {
|
|
ArrayList<Contact> contacts = new ArrayList<>();
|
|
for (Account account : getAccounts()) {
|
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
|
Contact contact = account.getRoster().getContactFromRoster(jid);
|
|
if (contact != null) {
|
|
contacts.add(contact);
|
|
}
|
|
}
|
|
}
|
|
return contacts;
|
|
}
|
|
|
|
public NotificationService getNotificationService() {
|
|
return this.mNotificationService;
|
|
}
|
|
|
|
public HttpConnectionManager getHttpConnectionManager() {
|
|
return this.mHttpConnectionManager;
|
|
}
|
|
|
|
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) {
|
|
markMessage(msg, Message.STATUS_WAITING);
|
|
this.resendMessage(msg);
|
|
}
|
|
}
|
|
|
|
public void clearConversationHistory(final Conversation conversation) {
|
|
conversation.clearMessages();
|
|
new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
databaseBackend.deleteMessagesInConversation(conversation);
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
public interface OnConversationUpdate {
|
|
public void onConversationUpdate();
|
|
}
|
|
|
|
public interface OnAccountUpdate {
|
|
public void onAccountUpdate();
|
|
}
|
|
|
|
public interface OnRosterUpdate {
|
|
public void onRosterUpdate();
|
|
}
|
|
|
|
public interface OnMucRosterUpdate {
|
|
public void onMucRosterUpdate();
|
|
}
|
|
|
|
private interface OnConferenceOptionsPushed {
|
|
public void onPushSucceeded();
|
|
public void onPushFailed();
|
|
}
|
|
|
|
public class XmppConnectionBinder extends Binder {
|
|
public XmppConnectionService getService() {
|
|
return XmppConnectionService.this;
|
|
}
|
|
}
|
|
|
|
public void sendBlockRequest(final Blockable blockable) {
|
|
if (blockable != null && blockable.getBlockedJid() != null) {
|
|
final Jid jid = blockable.getBlockedJid();
|
|
this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid), new OnIqPacketReceived() {
|
|
|
|
@Override
|
|
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
|
if (packet.getType() == IqPacket.TYPE_RESULT) {
|
|
account.getBlocklist().add(jid);
|
|
updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void sendUnblockRequest(final Blockable blockable) {
|
|
if (blockable != null && blockable.getJid() != null) {
|
|
final Jid jid = blockable.getBlockedJid();
|
|
this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() {
|
|
@Override
|
|
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
|
if (packet.getType() == IqPacket.TYPE_RESULT) {
|
|
account.getBlocklist().remove(jid);
|
|
updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|