package info.guardianproject.otr.app.im.plugin.xmpp;
import info.guardianproject.otr.OtrChatManager;
import info.guardianproject.otr.app.im.engine.Address;
import info.guardianproject.otr.app.im.engine.ChatGroupManager;
import info.guardianproject.otr.app.im.engine.ChatSession;
import info.guardianproject.otr.app.im.engine.ChatSessionManager;
import info.guardianproject.otr.app.im.engine.Contact;
import info.guardianproject.otr.app.im.engine.ContactList;
import info.guardianproject.otr.app.im.engine.ContactListListener;
import info.guardianproject.otr.app.im.engine.ContactListManager;
import info.guardianproject.otr.app.im.engine.ImConnection;
import info.guardianproject.otr.app.im.engine.ImErrorInfo;
import info.guardianproject.otr.app.im.engine.ImException;
import info.guardianproject.otr.app.im.engine.LoginInfo;
import info.guardianproject.otr.app.im.engine.Message;
import info.guardianproject.otr.app.im.engine.Presence;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence.Mode;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
import android.os.Parcel;
import android.util.Log;
public class XmppConnection extends ImConnection {
private final static String TAG = "Xmpp";
private XmppContactList mContactListManager;
private Contact mUser;
private MyXMPPConnection mConnection;
private XmppChatSessionManager mSessionManager;
private ConnectionConfiguration mConfig;
private XmppChatPacketListener mChatListener;
private OtrChatManager mOtrMgr;
private boolean mNeedReconnect;
private LoginInfo mLoginInfo;
private boolean mRetryLogin;
private Executor mExecutor;
private XmppConnection xmppConnection;
private ProxyInfo mProxyInfo = null;
public XmppConnection() {
Log.w(TAG, "created");
//ReconnectionManager.activate();
SmackConfiguration.setKeepAliveInterval(-1);
mExecutor = Executors.newSingleThreadExecutor();
xmppConnection = this;
}
public void sendMessage(org.jivesoftware.smack.packet.Message msg) {
// android.os.Debug.waitForDebugger();
if (mOtrMgr != null && mOtrMgr.isEncryptedSession(msg.getFrom(),msg.getTo()))
{
msg.setBody(mOtrMgr.sendMessage(msg.getFrom(),msg.getTo(), msg.getBody()));
}
mConnection.sendPacket(msg);
}
@Override
protected void doUpdateUserPresenceAsync(Presence presence) {
//android.os.Debug.waitForDebugger();
String statusText = presence.getStatusText();
Type type = Type.available;
Mode mode = Mode.available;
int priority = 20;
if (presence.getStatus() == Presence.AWAY) {
priority = 10;
mode = Mode.away;
}
else if (presence.getStatus() == Presence.IDLE) {
priority = 15;
mode = Mode.away;
}
else if (presence.getStatus() == Presence.DO_NOT_DISTURB) {
priority = 5;
mode = Mode.dnd;
}
else if (presence.getStatus() == Presence.OFFLINE) {
priority = 0;
type = Type.unavailable;
statusText = "Offline";
}
org.jivesoftware.smack.packet.Presence packet =
new org.jivesoftware.smack.packet.Presence(type, statusText, priority, mode);
mConnection.sendPacket(packet);
mUserPresence = presence;
notifyUserPresenceUpdated();
}
@Override
public int getCapability() {
// TODO chat groups
return 0;
}
@Override
public ChatGroupManager getChatGroupManager() {
// TODO chat groups
return null;
}
@Override
public ChatSessionManager getChatSessionManager() {
mSessionManager = new XmppChatSessionManager();
return mSessionManager;
}
@Override
public ContactListManager getContactListManager() {
mContactListManager = new XmppContactList();
return mContactListManager;
}
@Override
public Contact getLoginUser() {
return mUser;
}
@Override
public HashMap<String, String> getSessionContext() {
return null;
}
@Override
public int[] getSupportedPresenceStatus() {
return new int[] {
Presence.AVAILABLE,
Presence.AWAY,
Presence.IDLE,
Presence.OFFLINE,
Presence.DO_NOT_DISTURB,
};
}
@Override
public void loginAsync(final LoginInfo loginInfo, boolean retry) {
mLoginInfo = loginInfo;
mRetryLogin = retry;
mExecutor.execute(new Runnable() {
@Override
public void run() {
do_login();
}
});
}
private void do_login() {
if (mConnection != null) {
setState(getState(), new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, "still trying..."));
return;
}
Log.i(TAG, "logging in " + mLoginInfo.getUserName());
mNeedReconnect = true;
setState(LOGGING_IN, null);
mUserPresence = new Presence(Presence.AVAILABLE, "Online", null, null, Presence.CLIENT_TYPE_DEFAULT);
String username = mLoginInfo.getUserName();
String []comps = username.split("@");
if (comps.length != 2) {
disconnected(new ImErrorInfo(ImErrorInfo.INVALID_USERNAME, "username should be user@host"));
mRetryLogin = false;
mNeedReconnect = false;
return;
}
try {
initConnection(comps[1], comps[0], mLoginInfo.getPassword(), "Android");
} catch (XMPPException e) {
Log.e(TAG, "login failed", e);
mConnection = null;
ImErrorInfo info = new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, e.getMessage());
if (e.getMessage().contains("not-authorized")) {
Log.w(TAG, "not authorized - will not retry");
info = new ImErrorInfo(ImErrorInfo.INVALID_USERNAME, "invalid user/password");
disconnected(info);
mRetryLogin = false;
}
else if (mRetryLogin) {
Log.w(TAG, "will retry");
setState(LOGGING_IN, info);
}
else {
Log.w(TAG, "will not retry");
mConnection = null;
disconnected(info);
}
return;
} finally {
mNeedReconnect = false;
}
mUser = new Contact(new XmppAddress(comps[0], username), username);
setState(LOGGED_IN, null);
Log.i(TAG, "logged in");
}
public void setProxy (String type, String host, int port)
{
// android.os.Debug.waitForDebugger();
if (type == null)
{
mProxyInfo = ProxyInfo.forNoProxy();
}
else
{
ProxyInfo.ProxyType pType = ProxyType.valueOf(type);
mProxyInfo = new ProxyInfo(pType, host, port,"","");
}
}
private void initConnection(String serverHost, String login, String password, String resource) throws XMPPException {
//Android.os.Debug.waitForDebugger();
if (mProxyInfo == null)
mProxyInfo = ProxyInfo.forNoProxy();
if (serverHost.equals("gmail.com") || serverHost.equals("talk.google.com") || serverHost.equals("googlemail.com"))
{
mConfig = new ConnectionConfiguration("talk.google.com",5222,"gmail.com", mProxyInfo);
// You have to put this code before you login
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
mConfig.setSecurityMode(SecurityMode.required);
login = login + "@gmail.com";
}
else
{
//SASLAuthentication.supportSASLMechanism("DIGEST-MD5",0);
// SASLAuthentication.supportSASLMechanism("PLAIN", 0);
mConfig = new ConnectionConfiguration(serverHost, mProxyInfo);
//mConfig = new ConnectionConfiguration(serverHost, 5223, serverHost, mProxyInfo);
mConfig.setSecurityMode(SecurityMode.required);
// mConfig.setSASLAuthenticationEnabled(false);
//mConfig.setCompressionEnabled(false);
// mConfig.setSelfSignedCertificateEnabled(true);
// mConfig.setVerifyChainEnabled(false);
// mConfig.setVerifyRootCAEnabled(false);
}
mConfig.setReconnectionAllowed(true);
mConnection = new MyXMPPConnection(mConfig);
mChatListener = new XmppChatPacketListener(this);
mConnection.connect();
Log.i(TAG,"is secure connection? " + mConnection.isSecureConnection());
Log.i(TAG,"is using TLS? " + mConnection.isUsingTLS());
mConnection.addPacketListener(mChatListener, new MessageTypeFilter(org.jivesoftware.smack.packet.Message.Type.chat));
mConnection.addPacketListener(new PacketListener() {
@Override
public void processPacket(Packet packet) {
//android.os.Debug.waitForDebugger();
org.jivesoftware.smack.packet.Presence presence = (org.jivesoftware.smack.packet.Presence)packet;
if (presence.getType() == Type.subscribe) {
String address = parseAddressBase(presence.getFrom());
Log.i(TAG, "sub request from " + address);
Contact contact = findOrCreateContact(address);
mContactListManager.getSubscriptionRequestListener().onSubScriptionRequest(contact);
}
else if (presence.getType() == Type.available)
{
String address = parseAddressBase(presence.getFrom());
Log.i(TAG, "got 'available' from " + address);
}
else if (presence.getType() == Type.unavailable)
{
String address = parseAddressBase(presence.getFrom());
Log.i(TAG, "got 'unavailable' from " + address);
}
}
}, new PacketTypeFilter(org.jivesoftware.smack.packet.Presence.class));
mConnection.addConnectionListener(new ConnectionListener() {
@Override
public void reconnectionSuccessful() {
Log.i(TAG, "reconnection success");
setState(LOGGED_IN, null);
}
@Override
public void reconnectionFailed(Exception e) {
Log.i(TAG, "reconnection failed", e);
//forced_disconnect(new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, e.getMessage()));
}
@Override
public void reconnectingIn(int seconds) {
/*
* Reconnect happens:
* - due to network error
* - but not if connectionClosed is fired
*/
Log.i(TAG, "reconnecting in " + seconds);
setState(LOGGING_IN, null);
}
@Override
public void connectionClosedOnError(Exception e) {
/*
* This fires when:
* - Packet reader or writer detect an error
* - Stream compression failed
* - TLS fails but is required
*/
Log.i(TAG, "reconnect on error", e);
if (e.getMessage().contains("conflict")) {
disconnect();
disconnected(new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, "logged in from another location"));
}
else {
setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, e.getMessage()));
mExecutor.execute(new Runnable() {
@Override
public void run() {
maybe_reconnect();
}
});
}
}
@Override
public void connectionClosed() {
/*
* This can be called in these cases:
* - Connection is shutting down
* - because we are calling disconnect
* - in do_logout
*
* - NOT (fixed in smack)
* - because server disconnected "normally"
* - we were trying to log in (initConnection), but are failing
* - due to network error
* - in forced disconnect
* - due to login failing
*/
Log.i(TAG, "connection closed");
}
});
mConnection.login(login, password, resource);
org.jivesoftware.smack.packet.Presence presence =
new org.jivesoftware.smack.packet.Presence(org.jivesoftware.smack.packet.Presence.Type.available);
mConnection.sendPacket(presence);
}
void disconnected(ImErrorInfo info) {
Log.w(TAG, "disconnected");
setState(DISCONNECTED, info);
}
void forced_disconnect(ImErrorInfo info) {
// UNUSED
Log.w(TAG, "forced disconnect");
try {
if (mConnection!= null) {
XMPPConnection conn = mConnection;
mConnection = null;
conn.disconnect();
}
}
catch (Exception e) {
// Ignore
}
disconnected(info);
}
protected static String parseAddressBase(String from) {
return from.replaceFirst("/.*", "");
}
protected static String parseAddressUser(String from) {
return from.replaceFirst("@.*", "");
}
@Override
public void logoutAsync() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
do_logout();
}
});
}
private void do_logout() {
Log.w(TAG, "logout");
setState(LOGGING_OUT, null);
disconnect();
setState(DISCONNECTED, null);
}
private void disconnect() {
clearHeartbeat();
XMPPConnection conn = mConnection;
mConnection = null;
try {
conn.disconnect();
} catch (Throwable th) {
// ignore
}
mNeedReconnect = false;
mRetryLogin = false;
}
@Override
public void reestablishSessionAsync(HashMap<String, String> sessionContext) {
}
@Override
public void suspend() {
}
private ChatSession findOrCreateSession(String address) {
ChatSession session = mSessionManager.findSession(address);
if (session == null) {
Contact contact = findOrCreateContact(address);
session = mSessionManager.createChatSession(contact);
}
return session;
}
Contact findOrCreateContact(String address) {
Contact contact = mContactListManager.getContact(address);
if (contact == null) {
contact = makeContact(address);
}
return contact;
}
private static String makeNameFromAddress(String address) {
return address;
}
private static Contact makeContact(String address) {
Contact contact = new Contact(new XmppAddress(address), address);
return contact;
}
private final class XmppChatSessionManager extends ChatSessionManager {
@Override
protected void sendMessageAsync(ChatSession session, Message message) {
org.jivesoftware.smack.packet.Message msg =
new org.jivesoftware.smack.packet.Message(
message.getTo().getFullName(),
org.jivesoftware.smack.packet.Message.Type.chat
);
if (mOtrMgr != null && mOtrMgr.isEncryptedSession(msg.getFrom(),msg.getTo()))
{
msg.setBody(mOtrMgr.sendMessage(msg.getFrom(),msg.getTo(), message.getBody()));
}
else
msg.setBody(message.getBody());
mConnection.sendPacket(msg);
}
ChatSession findSession(String address) {
for (Iterator<ChatSession> iter = mSessions.iterator(); iter.hasNext();) {
ChatSession session = iter.next();
if (session.getParticipant().getAddress().getFullName().equals(address))
return session;
}
return null;
}
/**
* Start encryption for this chat
*/
public boolean encryptChat(String address)
{
Log.i(TAG, "Got encryptChat() request for: " + address);
if (address == null || address.length() == 0 || address.equals(mUser.getAddress().getFullName()))
return false;
if (mOtrMgr == null)
{
mOtrMgr = new OtrChatManager(xmppConnection);
mChatListener.setOtrMgr(mOtrMgr);
}
if (!mOtrMgr.isEncryptedSession(mUser.getAddress().getFullName(), address))
mOtrMgr.startSession(mUser.getAddress().getFullName(), address);
return true;
}
/**
* Stop encryption for this chat
*/
public boolean unencryptChat(String remoteAddress)
{
if (mOtrMgr != null)
{
mOtrMgr.endSession(mUser.getAddress().getFullName(), remoteAddress);
}
return true;
}
/**
* Start remote identity verification
*/
public void verifyRemoteIdentity(String address)
{
}
/**
* Get remote key fingerprint
*/
public String getRemoteKeyFingerprint(String remoteAddress)
{
return mOtrMgr.getRemoteKeyFingerprint(mUser.getAddress().getFullName(), remoteAddress);
}
/**
* Get remote key fingerprint
*/
public String getLocalKeyFingerprint(String remoteAddress)
{
return mOtrMgr.getLocalKeyFingerprint(mUser.getAddress().getFullName(), remoteAddress);
}
/**
* Start remote identity verification
*/
public boolean isEncryptedSession(String remoteAddress)
{
// android.os.Debug.waitForDebugger();
if (mOtrMgr!=null)
return (mOtrMgr.isEncryptedSession(mUser.getAddress().getFullName(), remoteAddress));
else
return false;
}
}
public ChatSession findSession (String address)
{
return mSessionManager.findSession(address);
}
public ChatSession createChatSession (Contact contact)
{
return mSessionManager.createChatSession(contact);
}
private final class XmppContactList extends ContactListManager {
private Hashtable<String, org.jivesoftware.smack.packet.Presence> unprocdPresence = new Hashtable<String, org.jivesoftware.smack.packet.Presence>();
@Override
protected void setListNameAsync(final String name, final ContactList list) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
do_setListName(name, list);
}
});
}
private void do_setListName(String name, ContactList list) {
Log.d(TAG, "set list name");
mConnection.getRoster().getGroup(list.getName()).setName(name);
notifyContactListNameUpdated(list, name);
}
@Override
public String normalizeAddress(String address) {
return address;
}
@Override
public void loadContactListsAsync() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
do_loadContactLists();
}
});
}
/**
* Create new list of contacts from roster entries.
*
* @param entryIter iterator of roster entries to add to contact list
* @param skipList list of contacts which should be omitted; new contacts are added to this list automatically
* @return contacts from roster which were not present in skiplist.
*/
private Collection<Contact> fillContacts(Iterator<RosterEntry> entryIter, Set<String> skipList) {
Collection<Contact> contacts = new ArrayList<Contact>();
for (; entryIter.hasNext();) {
RosterEntry entry = entryIter.next();
String address = parseAddressBase(entry.getUser());
/* Skip entries present in the skip list */
if (skipList != null && !skipList.add(address))
continue;
String name = entry.getName();
if (name == null)
name = address;
XmppAddress xaddress = new XmppAddress(name, address);
Contact contact = new Contact(xaddress, name);
contacts.add(contact);
}
return contacts;
}
private void do_loadContactLists() {
//android.os.Debug.waitForDebugger();
Log.d(TAG, "load contact lists");
Roster roster = mConnection.getRoster();
roster.setSubscriptionMode(Roster.SubscriptionMode.manual);
listenToRoster(roster);
Set<String> seen = new HashSet<String>();
for (Iterator<RosterGroup> giter = roster.getGroups().iterator(); giter.hasNext();) {
RosterGroup group = giter.next();
Collection<Contact> contacts = fillContacts(group.getEntries().iterator(), seen);
ContactList cl = new ContactList(mUser.getAddress(), group.getName(), true, contacts, this);
mContactLists.add(cl);
if (mDefaultContactList == null)
mDefaultContactList = cl;
notifyContactListLoaded(cl);
notifyContactsPresenceUpdated(contacts.toArray(new Contact[0]));
processQueuedPresenceNotifications (contacts);
}
if (roster.getUnfiledEntryCount() > 0) {
String generalGroupName = "General";
RosterGroup group = roster.getGroup(generalGroupName);
if (group == null)
group = roster.createGroup(generalGroupName);
Collection<Contact> contacts = fillContacts(roster.getUnfiledEntries().iterator(), null);
ContactList cl = new ContactList(mUser.getAddress(), generalGroupName , true, contacts, this);
//n8fr8: adding this contact list to the master list manager seems like the righ thing to do
mContactLists.add(cl);
mDefaultContactList = cl;
notifyContactListLoaded(cl);
processQueuedPresenceNotifications(contacts);
//n8fr8: also needs to ping the presence status change, too!
notifyContactsPresenceUpdated(contacts.toArray(new Contact[0]));
}
notifyContactListsLoaded();
}
/*
* iterators through a list of contacts to see if there were any Presence
* notifications sent before the contact was loaded
*/
private void processQueuedPresenceNotifications (Collection<Contact> contacts)
{
//now iterate through the list of queued up unprocessed presence changes
for (Contact contact : contacts)
{
String address = parseAddressBase(contact.getAddress().getFullName());
org.jivesoftware.smack.packet.Presence presence = unprocdPresence.get(address);
if (presence != null)
{
unprocdPresence.remove(address);
int type = Presence.AVAILABLE;
Mode rmode = presence.getMode();
Type rtype = presence.getType();
if (rmode == Mode.away || rmode == Mode.xa)
type = Presence.AWAY;
if (rmode == Mode.dnd)
type = Presence.DO_NOT_DISTURB;
if (rtype == Type.unavailable)
type = Presence.OFFLINE;
contact.setPresence(new Presence(type, presence.getStatus(), null, null, Presence.CLIENT_TYPE_DEFAULT));
}
}
}
private void listenToRoster(final Roster roster) {
roster.addRosterListener(new RosterListener() {
@Override
public void presenceChanged(org.jivesoftware.smack.packet.Presence presence) {
//android.os.Debug.waitForDebugger(); //this is for debugging a service
String user = parseAddressBase(presence.getFrom());
Contact contact = mContactListManager.getContact(user);
if (contact == null)
{
Log.d(TAG, "Got present update for NULL user: " + user);
//store the latest presence notification for this user in this queue
unprocdPresence.put(user, presence);
return;
}
Contact []contacts = new Contact[] { contact };
// Get it from the roster - it handles priorities, etc.
presence = roster.getPresence(user);
int type = Presence.AVAILABLE;
Mode rmode = presence.getMode();
Type rtype = presence.getType();
if (rmode == Mode.away || rmode == Mode.xa)
type = Presence.AWAY;
if (rmode == Mode.dnd)
type = Presence.DO_NOT_DISTURB;
if (rtype == Type.unavailable)
type = Presence.OFFLINE;
contact.setPresence(new Presence(type, presence.getStatus(), null, null, Presence.CLIENT_TYPE_DEFAULT));
notifyContactsPresenceUpdated(contacts);
}
@Override
public void entriesUpdated(Collection<String> addresses) {
// TODO update contact list entries from remote
Log.d(TAG, "roster entries updated");
}
@Override
public void entriesDeleted(Collection<String> addresses) {
// TODO delete contacts from remote
Log.d(TAG, "roster entries deleted");
}
@Override
public void entriesAdded(Collection<String> addresses) {
// TODO add contacts from remote
Log.d(TAG, "roster entries added");
}
});
}
@Override
protected ImConnection getConnection() {
return XmppConnection.this;
}
@Override
protected void doRemoveContactFromListAsync(Contact contact,
ContactList list) {
Roster roster = mConnection.getRoster();
String address = contact.getAddress().getFullName();
try {
RosterGroup group = roster.getGroup(list.getName());
if (group == null) {
Log.e(TAG, "could not find group " + list.getName() + " in roster");
return;
}
RosterEntry entry = roster.getEntry(address);
if (entry == null) {
Log.e(TAG, "could not find entry " + address + " in group " + list.getName());
return;
}
group.removeEntry(entry);
} catch (XMPPException e) {
Log.e(TAG, "remove entry failed", e);
throw new RuntimeException(e);
}
org.jivesoftware.smack.packet.Presence response =
new org.jivesoftware.smack.packet.Presence(org.jivesoftware.smack.packet.Presence.Type.unsubscribed);
response.setTo(address);
mConnection.sendPacket(response);
notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_REMOVED, contact);
}
@Override
protected void doDeleteContactListAsync(ContactList list) {
// TODO delete contact list
Log.i(TAG, "delete contact list " + list.getName());
}
@Override
protected void doCreateContactListAsync(String name,
Collection<Contact> contacts, boolean isDefault) {
// TODO create contact list
Log.i(TAG, "create contact list " + name + " default " + isDefault);
}
@Override
protected void doBlockContactAsync(String address, boolean block) {
// TODO block contact
}
@Override
protected void doAddContactToListAsync(String address, ContactList list)
throws ImException {
Log.i(TAG, "add contact to " + list.getName());
org.jivesoftware.smack.packet.Presence response =
new org.jivesoftware.smack.packet.Presence(org.jivesoftware.smack.packet.Presence.Type.subscribed);
response.setTo(address);
mConnection.sendPacket(response);
Roster roster = mConnection.getRoster();
String[] groups = new String[] { list.getName() };
try {
roster.createEntry(address, makeNameFromAddress(address), groups);
} catch (XMPPException e) {
throw new RuntimeException(e);
}
Contact contact = makeContact(address);
notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_ADDED, contact);
}
@Override
public void declineSubscriptionRequest(String contact) {
Log.d(TAG, "decline subscription");
org.jivesoftware.smack.packet.Presence response =
new org.jivesoftware.smack.packet.Presence(org.jivesoftware.smack.packet.Presence.Type.unsubscribed);
response.setTo(contact);
mConnection.sendPacket(response);
mContactListManager.getSubscriptionRequestListener().onSubscriptionDeclined(contact);
}
@Override
public void approveSubscriptionRequest(String contact) {
Log.d(TAG, "approve subscription");
try {
// FIXME maybe need to check if already in another contact list
mContactListManager.doAddContactToListAsync(contact, getDefaultContactList());
} catch (ImException e) {
Log.e(TAG, "failed to add " + contact + " to default list");
}
mContactListManager.getSubscriptionRequestListener().onSubscriptionApproved(contact);
}
@Override
public Contact createTemporaryContact(String address) {
Log.d(TAG, "create temporary " + address);
return makeContact(address);
}
}
public static class XmppAddress extends Address {
private String address;
private String name;
public XmppAddress() {
}
public XmppAddress(String name, String address) {
this.name = name;
this.address = address;
}
public XmppAddress(String address) {
this.name = makeNameFromAddress(address);
this.address = address;
}
@Override
public String getFullName() {
return address;
}
@Override
public String getScreenName() {
return name;
}
@Override
public void readFromParcel(Parcel source) {
name = source.readString();
address = source.readString();
}
@Override
public void writeToParcel(Parcel dest) {
dest.writeString(name);
dest.writeString(address);
}
}
private PacketCollector mPingCollector;
/*
* Alarm event fired
* @see info.guardianproject.otr.app.im.engine.ImConnection#sendHeartbeat()
*/
public void sendHeartbeat() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
doHeartbeat();
}
});
}
public void doHeartbeat() {
if (mConnection == null && mRetryLogin && mLoginInfo != null) {
Log.i(TAG, "reconnect with login");
do_login();
}
if (mConnection == null)
return;
if (mNeedReconnect) {
retry_reconnect();
}
else if (mConnection.isConnected() && getState() == LOGGED_IN) {
Log.d(TAG, "ping");
if (!sendPing()) {
if (getState() == LOGGED_IN)
setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network timeout"));
Log.w(TAG, "reconnect on ping failed");
force_reconnect();
}
}
}
private void clearHeartbeat() {
Log.d(TAG, "clear heartbeat");
mPingCollector = null;
}
private boolean sendPing() {
// Check ping result from previous send
if (mPingCollector != null) {
IQ result = (IQ)mPingCollector.nextResult(0);
mPingCollector.cancel();
if (result == null)
{
clearHeartbeat();
Log.e(TAG, "ping timeout");
return false;
}
}
IQ req = new IQ() {
public String getChildElementXML() {
return "<ping xmlns='urn:xmpp:ping'/>";
}
};
req.setType(IQ.Type.GET);
PacketFilter filter = new AndFilter(new PacketIDFilter(req.getPacketID()),
new PacketTypeFilter(IQ.class));
mPingCollector = mConnection.createPacketCollector(filter);
mConnection.sendPacket(req);
return true;
}
static class MyXMPPConnection extends XMPPConnection {
public MyXMPPConnection(ConnectionConfiguration config) {
super(config);
//this.getConfiguration().setSocketFactory(arg0)
}
}
@Override
public void networkTypeChanged() {
super.networkTypeChanged();
if (getState() == LOGGED_IN)
setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network changed"));
if (getState() != LOGGED_IN && getState() != LOGGING_IN)
return;
if (mNeedReconnect)
return;
Log.w(TAG, "reconnect on network change");
force_reconnect();
}
/*
* Force a disconnect and reconnect, unless we are already reconnecting.
*/
private void force_reconnect() {
Log.d(TAG, "force_reconnect need=" + mNeedReconnect);
if (mConnection == null)
return;
if (mNeedReconnect)
return;
mConnection.disconnect();
mNeedReconnect = true;
reconnect();
}
/*
* Reconnect unless we are already in the process of doing so.
*/
private void maybe_reconnect() {
// If we already know we don't have a good connection, the heartbeat will take care of this
Log.d(TAG, "maybe_reconnect need=" + mNeedReconnect);
if (mNeedReconnect)
return;
mNeedReconnect = true;
reconnect();
}
/*
* Retry a reconnect on alarm event
*/
private void retry_reconnect() {
// Retry reconnecting if we still need to
Log.d(TAG, "retry_reconnect need=" + mNeedReconnect);
if (mConnection != null && mNeedReconnect)
reconnect();
}
/*
* Retry connecting
*/
private void reconnect() {
Log.i(TAG, "reconnect");
clearHeartbeat();
try {
mConnection.connect();
mNeedReconnect = false;
Log.i(TAG, "reconnected");
setState(LOGGED_IN, null);
} catch (XMPPException e) {
Log.e(TAG, "reconnection on network change failed", e);
setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, e.getMessage()));
}
}
/**
* @return the mOtrMgr
*/
public OtrChatManager getOtrManager() {
return mOtrMgr;
}
/**
* @param mOtrMgr the mOtrMgr to set
*/
public void setOtrManager(OtrChatManager mOtrMgr) {
this.mOtrMgr = mOtrMgr;
}
}