package org.awesomeapp.messenger.plugin.xmpp; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import org.awesomeapp.messenger.ImApp; import org.awesomeapp.messenger.crypto.TorProxyInfo; import org.awesomeapp.messenger.crypto.omemo.Omemo; import org.awesomeapp.messenger.model.Address; import org.awesomeapp.messenger.model.ChatGroup; import org.awesomeapp.messenger.model.ChatGroupManager; import org.awesomeapp.messenger.model.ChatSession; import org.awesomeapp.messenger.model.ChatSessionManager; import org.awesomeapp.messenger.model.Contact; import org.awesomeapp.messenger.model.ContactList; import org.awesomeapp.messenger.model.ContactListListener; import org.awesomeapp.messenger.model.ContactListManager; import org.awesomeapp.messenger.model.ImConnection; import org.awesomeapp.messenger.model.ImEntity; import org.awesomeapp.messenger.model.ImErrorInfo; import org.awesomeapp.messenger.model.ImException; import org.awesomeapp.messenger.model.Invitation; import org.awesomeapp.messenger.model.Message; import org.awesomeapp.messenger.model.Presence; import org.awesomeapp.messenger.provider.Imps; import org.awesomeapp.messenger.provider.ImpsErrorInfo; import org.awesomeapp.messenger.service.IChatSession; import org.awesomeapp.messenger.service.adapters.ChatSessionAdapter; import org.awesomeapp.messenger.ui.legacy.DatabaseUtils; import org.awesomeapp.messenger.util.Debug; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.PresenceListener; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat.Chat; import org.jivesoftware.smack.chat.ChatManager; import org.jivesoftware.smack.filter.StanzaTypeFilter; import org.jivesoftware.smack.packet.DefaultExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.StandardExtensionElement; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.roster.RosterGroup; import org.jivesoftware.smack.roster.RosterListener; import org.jivesoftware.smack.roster.packet.RosterPacket; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.dns.HostAddress; import org.jivesoftware.smackx.address.provider.MultipleAddressesProvider; import org.jivesoftware.smackx.bytestreams.socks5.provider.BytestreamsProvider; import org.jivesoftware.smackx.chatstates.ChatState; import org.jivesoftware.smackx.chatstates.ChatStateManager; import org.jivesoftware.smackx.chatstates.provider.ChatStateExtensionProvider; import org.jivesoftware.smackx.commands.provider.AdHocCommandDataProvider; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.provider.DiscoverInfoProvider; import org.jivesoftware.smackx.disco.provider.DiscoverItemsProvider; import org.jivesoftware.smackx.iqlast.LastActivityManager; import org.jivesoftware.smackx.iqlast.packet.LastActivity; import org.jivesoftware.smackx.iqprivate.PrivateDataManager; import org.jivesoftware.smackx.muc.InvitationListener; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.MultiUserChatManager; import org.jivesoftware.smackx.muc.Occupant; import org.jivesoftware.smackx.muc.ParticipantStatusListener; import org.jivesoftware.smackx.muc.RoomInfo; import org.jivesoftware.smackx.muc.SubjectUpdatedListener; import org.jivesoftware.smackx.muc.packet.GroupChatInvitation; import org.jivesoftware.smackx.muc.packet.MUCUser; import org.jivesoftware.smackx.muc.provider.MUCAdminProvider; import org.jivesoftware.smackx.muc.provider.MUCOwnerProvider; import org.jivesoftware.smackx.muc.provider.MUCUserProvider; import org.jivesoftware.smackx.offline.packet.OfflineMessageInfo; import org.jivesoftware.smackx.offline.packet.OfflineMessageRequest; import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation; import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; import org.jivesoftware.smackx.ping.PingManager; import org.jivesoftware.smackx.ping.provider.PingProvider; import org.jivesoftware.smackx.privacy.PrivacyListManager; import org.jivesoftware.smackx.privacy.packet.PrivacyItem; import org.jivesoftware.smackx.privacy.provider.PrivacyProvider; import org.jivesoftware.smackx.receipts.DeliveryReceipt; import org.jivesoftware.smackx.receipts.DeliveryReceiptManager; import org.jivesoftware.smackx.receipts.DeliveryReceiptRequest; import org.jivesoftware.smackx.search.UserSearch; import org.jivesoftware.smackx.sharedgroups.packet.SharedGroupsInfo; import org.jivesoftware.smackx.si.provider.StreamInitiationProvider; import org.jivesoftware.smackx.vcardtemp.VCardManager; import org.jivesoftware.smackx.vcardtemp.packet.VCard; import org.jivesoftware.smackx.vcardtemp.provider.VCardProvider; import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.provider.DataFormProvider; import org.jivesoftware.smackx.xhtmlim.provider.XHTMLExtensionProvider; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import de.duenndns.ssl.MemorizingTrustManager; import im.zom.messenger.R; public class XmppConnection extends ImConnection { private static final String DISCO_FEATURE = "http://jabber.org/protocol/disco#info"; final static String TAG = "ZomXMPP"; private final static boolean PING_ENABLED = true; private XmppContactListManager mContactListManager; private Contact mUser; private boolean mUseTor; // watch out, this is a different XMPPConnection class than XmppConnection! ;) // Synchronized by executor thread private XMPPTCPConnection mConnection; private XmppStreamHandler mStreamHandler; private ChatManager mChatManager; private Roster mRoster; private XmppChatSessionManager mSessionManager; private XMPPTCPConnectionConfiguration.Builder mConfig; private Omemo mOmemoInstance; // True if we are in the process of reconnecting. Reconnection is retried once per heartbeat. // Synchronized by executor thread. private boolean mNeedReconnect; private boolean mRetryLogin; private ThreadPoolExecutor mExecutor; private Timer mTimerPresence; private ProxyInfo mProxyInfo = null; private long mAccountId = -1; private long mProviderId = -1; private boolean mIsGoogleAuth = false; private final static String SSLCONTEXT_TYPE = "TLS"; private static SSLContext sslContext; private MemorizingTrustManager mMemTrust; private final static int SOTIMEOUT = 1000 * 120; private final static int CONNECT_TIMEOUT = 1000 * 30; private PingManager mPingManager; private String mUsername; private String mPassword; private String mResource; private int mPriority; private int mGlobalId; private static int mGlobalCount; private SecureRandom rndForTorCircuits = null; // Maintains a sequence counting up to the user configured heartbeat interval private int heartbeatSequence = 0; private LinkedList<String> qAvatar = new LinkedList <String>(); private LinkedList<org.jivesoftware.smack.packet.Presence> qPresence = new LinkedList<org.jivesoftware.smack.packet.Presence>(); private LinkedList<org.jivesoftware.smack.packet.Stanza> qPacket = new LinkedList<org.jivesoftware.smack.packet.Stanza>(); private LinkedList<Contact> qNewContact = new LinkedList<Contact>(); private final static String DEFAULT_CONFERENCE_SERVER = "conference.zom.im"; private final static String PRIVACY_LIST_DEFAULT = "defaultprivacylist"; public XmppConnection(Context context) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException { super(context); synchronized (XmppConnection.class) { mGlobalId = mGlobalCount++; } Debug.onConnectionStart(); SmackConfiguration.setDefaultPacketReplyTimeout(SOTIMEOUT); // Create a single threaded executor. This will serialize actions on the underlying connection. createExecutor(); addProviderManagerExtensions(); XmppStreamHandler.addExtensionProviders(); // DeliveryReceipts.addExtensionProviders(); // ServiceDiscoveryManager.setIdentityName("ChatSecure"); // ServiceDiscoveryManager.setIdentityType("phone"); } public void initUser(long providerId, long accountId) throws ImException { ContentResolver contentResolver = mContext.getContentResolver(); Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(providerId)}, null); if (cursor == null) throw new ImException("unable to query settings"); Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( cursor, contentResolver, providerId, false, null); mProviderId = providerId; mAccountId = accountId; mUser = makeUser(providerSettings, contentResolver); mUseTor = providerSettings.getUseTor(); providerSettings.close(); } private synchronized Contact makeUser(Imps.ProviderSettings.QueryMap providerSettings, ContentResolver contentResolver) { Contact contactUser = null; String nickname = Imps.Account.getNickname(contentResolver, mAccountId); String userName = Imps.Account.getUserName(contentResolver, mAccountId); String domain = providerSettings.getDomain(); String xmppName = userName + '@' + domain + '/' + providerSettings.getXmppResource(); contactUser = new Contact(new XmppAddress(xmppName), nickname); return contactUser; } private void createExecutor() { mExecutor = new ThreadPoolExecutor(1, 1, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); } private boolean execute(Runnable runnable) { if (mExecutor == null) createExecutor (); //if we disconnected, will need to recreate executor here, because join() made it null try { mExecutor.execute(runnable); } catch (RejectedExecutionException ex) { return false; } return true; } // Execute a runnable only if we are idle private boolean executeIfIdle(Runnable runnable) { if (mExecutor.getActiveCount() + mExecutor.getQueue().size() == 0) { return execute(runnable); } return false; } // This runs in executor thread, and since there is only one such thread, we will definitely // succeed in shutting down the executor if we get here. public void join() { final ExecutorService executor = mExecutor; mExecutor = null; // This will send us an interrupt, which we will ignore. We will terminate // anyway after the caller is done. This also drains the executor queue. if (executor != null) executor.shutdownNow(); } // For testing boolean joinGracefully() throws InterruptedException { final ExecutorService executor = mExecutor; mExecutor = null; // This will send us an interrupt, which we will ignore. We will terminate // anyway after the caller is done. This also drains the executor queue. if (executor != null) { executor.shutdown(); return executor.awaitTermination(1, TimeUnit.SECONDS); } return false; } public void sendPacket(org.jivesoftware.smack.packet.Stanza packet) { qPacket.add(packet); } void postpone(final org.jivesoftware.smack.packet.Stanza packet) { if (packet instanceof org.jivesoftware.smack.packet.Message) { boolean groupChat = ((org.jivesoftware.smack.packet.Message) packet).getType().equals( org.jivesoftware.smack.packet.Message.Type.groupchat); ChatSession session = findOrCreateSession(packet.getTo().toString(), groupChat); if (session != null) session.onMessagePostponed(packet.getStanzaId()); } } private boolean mLoadingAvatars = false; private void loadVCardsAsync () { if (!mLoadingAvatars) { executeIfIdle(new AvatarLoader()); } } private class AvatarLoader implements Runnable { @Override public void run () { mLoadingAvatars = true; ContentResolver resolver = mContext.getContentResolver(); try { while (qAvatar.size()>0) { loadVCard (resolver, qAvatar.poll()); } } catch (Exception e) {} mLoadingAvatars = false; } } private boolean loadVCard (ContentResolver resolver, String jid) { try { debug(TAG, "loading vcard for: " + jid); EntityBareJid bareJid = JidCreate.entityBareFrom(jid); VCardManager vCardManager = VCardManager.getInstanceFor(mConnection); VCard vCard = vCardManager.loadVCard(bareJid); Contact contact = mContactListManager.getContact(bareJid.toString()); if (!TextUtils.isEmpty(vCard.getNickName())) { if (!vCard.getNickName().equals(contact.getName())) { contact.setName(vCard.getNickName()); mContactListManager.doSetContactName(contact.getAddress().getBareAddress(), contact.getName()); } } //check for a forwarding address if (vCard.getJabberId() != null && (!vCard.getJabberId().equals(bareJid.toString()))) { contact.setForwardingAddress(vCard.getJabberId()); } // If VCard is loaded, then save the avatar to the personal folder. String avatarHash = vCard.getAvatarHash(); if (avatarHash != null) { byte[] avatarBytes = vCard.getAvatar(); if (avatarBytes != null) { debug(TAG, "found avatar image in vcard for: " + bareJid.toString()); debug(TAG, "start avatar length: " + avatarBytes.length); DatabaseUtils.insertAvatarBlob(resolver, Imps.Avatars.CONTENT_URI, mProviderId, mAccountId, avatarBytes, avatarHash, bareJid.toString()); return true; } } } catch (Exception e) { debug(TAG, "err loading vcard: " + e.toString()); if (e.getMessage() != null) { String streamErr = e.getMessage(); if (streamErr != null && (streamErr.contains("404") || streamErr.contains("503"))) { return false; } } } return false; } @Override protected void doUpdateUserPresenceAsync(Presence presence) { org.jivesoftware.smack.packet.Presence packet = makePresencePacket(presence); sendPacket(packet); mUserPresence = presence; notifyUserPresenceUpdated(); } private org.jivesoftware.smack.packet.Presence makePresencePacket(Presence presence) { String statusText = presence.getStatusText(); org.jivesoftware.smack.packet.Presence.Type type = org.jivesoftware.smack.packet.Presence.Type.available; org.jivesoftware.smack.packet.Presence.Mode mode = org.jivesoftware.smack.packet.Presence.Mode.available; int priority = mPriority; final int status = presence.getStatus(); if (status == Presence.AWAY) { priority = 10; mode = org.jivesoftware.smack.packet.Presence.Mode.away; } else if (status == Presence.IDLE) { priority = 15; mode = org.jivesoftware.smack.packet.Presence.Mode.away; } else if (status == Presence.DO_NOT_DISTURB) { priority = 5; mode = org.jivesoftware.smack.packet.Presence.Mode.dnd; } else if (status == Presence.OFFLINE) { priority = 0; type = org.jivesoftware.smack.packet.Presence.Type.unavailable; statusText = "Offline"; } // The user set priority is the maximum allowed if (priority > mPriority) priority = mPriority; org.jivesoftware.smack.packet.Presence packet = new org.jivesoftware.smack.packet.Presence( type, statusText, priority, mode); try { byte[] avatar = DatabaseUtils.getAvatarBytesFromAddress(mContext.getContentResolver(), mUser.getAddress().getBareAddress()); if (avatar != null) { VCardTempXUpdatePresenceExtension vcardExt = new VCardTempXUpdatePresenceExtension(avatar); packet.addExtension(vcardExt); } } catch (Exception e) { debug(TAG,"error upading presence with avatar hash",e); } return packet; } @Override public int getCapability() { return ImConnection.CAPABILITY_SESSION_REESTABLISHMENT | ImConnection.CAPABILITY_GROUP_CHAT; } private XmppChatGroupManager mChatGroupManager = null; @Override public synchronized ChatGroupManager getChatGroupManager() { if (mChatGroupManager == null) mChatGroupManager = new XmppChatGroupManager(); return mChatGroupManager; } public class XmppChatGroupManager extends ChatGroupManager { private Hashtable<String,MultiUserChat> mMUCs = new Hashtable<String,MultiUserChat>(); public MultiUserChat getMultiUserChat (String chatRoomJid) { return mMUCs.get(chatRoomJid); } public void reconnectAll () { Enumeration<MultiUserChat> eMuc = mMUCs.elements(); while (eMuc.hasMoreElements()) { MultiUserChat muc = eMuc.nextElement(); if (!muc.isJoined()) { try { //muc.join(muc.getNickname()); muc.join(muc.getNickname()); } catch (Exception e) { e.printStackTrace(); } } } } @Override public String getDefaultGroupChatService () { try { // Create a MultiUserChat using a Connection for a room //TODO fix this with new smack MultiUserChatManager mucMgr = MultiUserChatManager.getInstanceFor(mConnection); Collection<DomainBareJid> servers = mucMgr.getXMPPServiceDomains(); //just grab the first one for (DomainBareJid server : servers) return server.toString(); } catch (Exception xe) { //unable to find conference server return DEFAULT_CONFERENCE_SERVER; } return DEFAULT_CONFERENCE_SERVER; } @Override public boolean createChatGroupAsync(String chatRoomJid, String subject, String nickname) throws Exception { if (mConnection == null || getState() != ImConnection.LOGGED_IN) return false; RoomInfo roomInfo = null; // Create a MultiUserChat using a Connection for a room MultiUserChatManager mucMgr = MultiUserChatManager.getInstanceFor(mConnection); if (chatRoomJid.endsWith("@")) { //let's add a host to that! Collection<DomainBareJid> servers = mucMgr.getXMPPServiceDomains(); if (servers.iterator().hasNext()) chatRoomJid += servers.iterator().next().toString(); else { chatRoomJid += DEFAULT_CONFERENCE_SERVER; } } Address address = new XmppAddress (chatRoomJid); String[] parts = chatRoomJid.split("@"); String room = parts[0]; String server = parts[1]; if (TextUtils.isEmpty(subject)) subject = room; if (TextUtils.isEmpty(nickname)) nickname = mUsername; try { MultiUserChat muc = mucMgr.getMultiUserChat(JidCreate.entityBareFrom(chatRoomJid)); boolean mucCreated = false; try { // Create the room MultiUserChat.MucCreateConfigFormHandle handle = muc.createOrJoin(Resourcepart.from(nickname)); mucCreated = true; } catch (Exception iae) { if (iae.getMessage().contains("Creation failed")) { //some server's don't return the proper 201 create code, so we can just assume the room was created! } else { throw iae; } } ChatGroup chatGroup = mGroups.get(chatRoomJid); if (chatGroup == null) { chatGroup = new ChatGroup(address, subject, this); mGroups.put(chatRoomJid, chatGroup); } mMUCs.put(chatRoomJid, muc); try { Form form = muc.getConfigurationForm(); Form submitForm = form.createAnswerForm(); for (FormField field : form.getFields()) { if (!(field.getType() == FormField.Type.hidden) && field.getVariable() != null) { submitForm.setDefaultAnswer(field.getVariable()); } } // Sets the new owner of the room if (submitForm.getField("muc#roomconfig_roomowners") != null) { List owners = new ArrayList(); owners.add(mUser.getAddress().getBareAddress()); submitForm.setAnswer("muc#roomconfig_roomowners", owners); } if (submitForm.getField("muc#roomconfig_roomname") != null) submitForm.setAnswer("muc#roomconfig_roomname", subject); if (submitForm.getField("muc#roomconfig_roomdesc") != null) submitForm.setAnswer("muc#roomconfig_roomdesc", subject); if (submitForm.getField("muc#roomconfig_changesubject") != null) submitForm.setAnswer("muc#roomconfig_changesubject", true); if (submitForm.getField("muc#roomconfig_anonymity") != null) submitForm.setAnswer("muc#roomconfig_anonymity", "nonanonymous"); if (submitForm.getField("muc#roomconfig_publicroom") != null) submitForm.setAnswer("muc#roomconfig_publicroom", false); if (submitForm.getField("muc#roomconfig_persistentroom") != null) submitForm.setAnswer("muc#roomconfig_persistentroom", true); if (submitForm.getField("muc#roomconfig_whois") != null) submitForm.setAnswer("muc#roomconfig_whois", Arrays.asList("anyone")); // if (submitForm.getField("muc#roomconfig_historylength") != null) // submitForm.setAnswer("muc#roomconfig_historylength", 0); // if (submitForm.getField("muc#maxhistoryfetch") != null) // submitForm.setAnswer("muc#maxhistoryfetch", 0); if (submitForm.getField("muc#roomconfig_enablelogging") != null) submitForm.setAnswer("muc#roomconfig_enablelogging", false); // if (submitForm.getField("muc#maxhistoryfetch") != null) // submitForm.setAnswer("muc#maxhistoryfetch", 0); muc.sendConfigurationForm(submitForm); if (TextUtils.isEmpty(muc.getSubject())) muc.changeSubject(subject); else chatGroup.setName(muc.getSubject()); } catch (XMPPException xe) { debug(TAG, "(ignoring) got an error configuring MUC room: " + xe.getLocalizedMessage()); } List<EntityFullJid> mucOccupant = muc.getOccupants(); for (EntityFullJid occupantAddress : mucOccupant) { Occupant occupant = muc.getOccupant(occupantAddress); XmppAddress xa = new XmppAddress(occupant.getJid().toString()); Contact mucContact = new Contact(xa,xa.getResource()); org.jivesoftware.smack.packet.Presence presence = muc.getOccupantPresence(occupantAddress); if (presence != null) { ExtensionElement packetExtension = presence.getExtension("x", "vcard-temp:x:update"); if (packetExtension != null) { DefaultExtensionElement o = (DefaultExtensionElement) packetExtension; String hash = o.getValue("photo"); if (hash != null) { //boolean hasMatches = DatabaseUtils.doesAvatarHashExist(mContext.getContentResolver(), Imps.Avatars.CONTENT_URI, chatGroup.getAddress().getAddress(), hash); //if (!hasMatches) //we must reload // qAvatar.push(chatGroup.getAddress().getAddress()); } else { //no avatar, so update it since it will be small! // qAvatar.push(chatGroup.getAddress().getAddress()); } } } // Presence p = new Presence(parsePresence(presence), presence.getStatus(), null, null, Presence.CLIENT_TYPE_DEFAULT); // mucContact.setPresence(p); chatGroup.addMemberAsync(mucContact); } addMucListeners(muc); return true; } catch (XMPPException e) { debug(TAG,"error creating MUC",e); return false; } } @Override public void deleteChatGroupAsync(ChatGroup group) { String chatRoomJid = group.getAddress().getAddress(); if (mMUCs.containsKey(chatRoomJid)) { MultiUserChat muc = mMUCs.get(chatRoomJid); try { //muc.destroy("", null); mMUCs.remove(chatRoomJid); } catch (Exception e) { debug(TAG,"error destroying MUC",e); } } } @Override protected void addGroupMemberAsync(ChatGroup group, Contact contact) { // inviteUserAsync(group, contact); //we already have invite, so... what is this? } @Override protected void removeGroupMemberAsync(ChatGroup group, Contact contact) { String chatRoomJid = group.getAddress().getAddress(); if (mMUCs.containsKey(chatRoomJid)) { MultiUserChat muc = mMUCs.get(chatRoomJid); try { String reason = ""; muc.kickParticipant(Resourcepart.from(contact.getName()),reason); // muc.kickParticipant(chatRoomJid, contact.getAddress().getBareAddress()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void joinChatGroupAsync(Address address, String subject) { String chatRoomJid = address.getBareAddress(); String[] parts = chatRoomJid.split("@"); String room = parts[0]; String server = parts[1]; String nickname = mUser.getName();//.split("@")[0]; try { // Create a MultiUserChat using a Connection for a room MultiUserChatManager mucMgr = MultiUserChatManager.getInstanceFor(mConnection); MultiUserChat muc = mucMgr.getMultiUserChat( JidCreate.entityBareFrom(chatRoomJid)); muc.join(Resourcepart.from(nickname)); if (TextUtils.isEmpty(subject)) subject = room; ChatGroup chatGroup = mGroups.get(chatRoomJid); if (chatGroup == null) { chatGroup = new ChatGroup(address, subject, this); mGroups.put(chatRoomJid, chatGroup); } mMUCs.put(chatRoomJid, muc); List<EntityFullJid> mucOccupant = muc.getOccupants(); for (EntityFullJid occupant : mucOccupant) { XmppAddress xa = new XmppAddress(occupant.toString()); Contact mucContact = new Contact(xa,xa.getResource()); org.jivesoftware.smack.packet.Presence presence = muc.getOccupantPresence(occupant); Presence p = new Presence(parsePresence(presence), null, null, null, Presence.CLIENT_TYPE_MOBILE); mucContact.setPresence(p); chatGroup.addMemberAsync(mucContact); } addMucListeners(muc); } catch (Exception e) { debug(TAG,"error joining MUC",e); } } private void addMucListeners (MultiUserChat muc) { muc.addSubjectUpdatedListener(new SubjectUpdatedListener() { @Override public void subjectUpdated(String subject, EntityFullJid from) { XmppAddress xa = new XmppAddress(from.toString()); MultiUserChat muc = mChatGroupManager.getMultiUserChat(xa.getBareAddress()); ChatGroup chatGroup = mChatGroupManager.getChatGroup(xa); chatGroup.setName(subject); } }); muc.addParticipantStatusListener(new ParticipantStatusListener() { @Override public void joined(EntityFullJid entityFullJid) { XmppAddress xa = new XmppAddress(entityFullJid.toString()); MultiUserChat muc = mChatGroupManager.getMultiUserChat(xa.getBareAddress()); ChatGroup chatGroup = mChatGroupManager.getChatGroup(xa); Contact mucContact = new Contact(xa, xa.getResource()); Presence p = new Presence(Imps.Presence.AVAILABLE, null, null, null, Presence.CLIENT_TYPE_MOBILE); org.jivesoftware.smack.packet.Presence presence = muc.getOccupantPresence(entityFullJid); if (presence != null) { ExtensionElement packetExtension = presence.getExtension("x", "vcard-temp:x:update"); if (packetExtension != null) { DefaultExtensionElement o = (DefaultExtensionElement) packetExtension; String hash = o.getValue("photo"); if (hash != null) { // boolean hasMatches = DatabaseUtils.doesAvatarHashExist(mContext.getContentResolver(), Imps.Avatars.CONTENT_URI, chatGroup.getAddress().getAddress(), hash); // if (!hasMatches) //we must reload // qAvatar.push(chatGroup.getAddress().getAddress()); } } } mucContact.setPresence(p); chatGroup.addMemberAsync(mucContact); } @Override public void left(EntityFullJid entityFullJid) { XmppAddress xa = new XmppAddress(entityFullJid.toString()); MultiUserChat muc = mChatGroupManager.getMultiUserChat(xa.getBareAddress()); ChatGroup chatGroup = mChatGroupManager.getChatGroup(xa); Contact mucContact = new Contact(xa, xa.getResource()); chatGroup.removeMemberAsync(mucContact); } @Override public void kicked(EntityFullJid entityFullJid, Jid jid, String s) { } @Override public void voiceGranted(EntityFullJid entityFullJid) { } @Override public void voiceRevoked(EntityFullJid entityFullJid) { } @Override public void banned(EntityFullJid entityFullJid, Jid jid, String s) { } @Override public void membershipGranted(EntityFullJid entityFullJid) { } @Override public void membershipRevoked(EntityFullJid entityFullJid) { } @Override public void moderatorGranted(EntityFullJid entityFullJid) { } @Override public void moderatorRevoked(EntityFullJid entityFullJid) { } @Override public void ownershipGranted(EntityFullJid entityFullJid) { } @Override public void ownershipRevoked(EntityFullJid entityFullJid) { } @Override public void adminGranted(EntityFullJid entityFullJid) { } @Override public void adminRevoked(EntityFullJid entityFullJid) { } @Override public void nicknameChanged(EntityFullJid entityFullJid, Resourcepart resourcepart) { } }); muc.addParticipantListener(new PresenceListener() { @Override public void processPresence(org.jivesoftware.smack.packet.Presence presence) { XmppAddress xa = new XmppAddress(presence.getFrom().toString()); Contact mucContact = new Contact(xa, xa.getResource()); Presence p = new Presence(parsePresence(presence), presence.getStatus(), null, null, Presence.CLIENT_TYPE_DEFAULT); mucContact.setPresence(p); } }); } @Override public void leaveChatGroupAsync(ChatGroup group) { String chatRoomJid = group.getAddress().getBareAddress(); if (mMUCs.containsKey(chatRoomJid)) { MultiUserChat muc = mMUCs.get(chatRoomJid); try { muc.leave(); } catch (Exception nce) { Log.e(ImApp.LOG_TAG,"not connected error trying to leave group",nce); } mMUCs.remove(chatRoomJid); } } @Override public void inviteUserAsync(final ChatGroup group, final Contact invitee) { execute(new Runnable () { public void run() { String chatRoomJid = group.getAddress().getAddress(); if (mMUCs.containsKey(chatRoomJid)) { MultiUserChat muc = mMUCs.get(chatRoomJid); String reason = group.getName(); //no reason for now try { muc.invite(JidCreate.entityBareFrom(invitee.getAddress().getAddress()), reason); muc.grantMembership(JidCreate.entityBareFrom(invitee.getAddress().getAddress())); } catch (Exception nce) { Log.e(ImApp.LOG_TAG, "not connected error trying to add invite", nce); } } } }); } @Override public void acceptInvitationAsync(Invitation invitation) { Address addressGroup = invitation.getGroupAddress(); joinChatGroupAsync (addressGroup,invitation.getReason()); } @Override public void rejectInvitationAsync(Invitation invitation) { Address addressGroup = invitation.getGroupAddress(); String reason = ""; // no reason for now MultiUserChatManager mucMgr = MultiUserChatManager.getInstanceFor(mConnection); try { mucMgr.decline(JidCreate.entityBareFrom(addressGroup.getAddress()), JidCreate.entityBareFrom(invitation.getSender().getAddress()), reason); } catch (Exception nce) { Log.e(ImApp.LOG_TAG,"not connected error trying to reject invite",nce); } } }; @Override public synchronized ChatSessionManager getChatSessionManager() { if (mSessionManager == null) mSessionManager = new XmppChatSessionManager(); return mSessionManager; } @Override public synchronized XmppContactListManager getContactListManager() { if (mContactListManager == null) mContactListManager = new XmppContactListManager(); return mContactListManager; } @Override public Contact getLoginUser() { return mUser; } @Override public Map<String, String> getSessionContext() { // Empty state for now (but must have at least one key) return Collections.singletonMap("state", "empty"); } @Override public int[] getSupportedPresenceStatus() { return new int[] { Presence.AVAILABLE, Presence.AWAY, Presence.IDLE, Presence.OFFLINE, Presence.DO_NOT_DISTURB, }; } @Override public boolean isUsingTor() { return mUseTor; } @Override public void loginAsync(long accountId, String passwordTemp, long providerId, boolean retry) { mAccountId = accountId; mPassword = passwordTemp; mProviderId = providerId; mRetryLogin = retry; ContentResolver contentResolver = mContext.getContentResolver(); if (mPassword == null) mPassword = Imps.Account.getPassword(contentResolver, mAccountId); mIsGoogleAuth = false;// mPassword.startsWith(GTalkOAuth2.NAME); if (mIsGoogleAuth) { mPassword = mPassword.split(":")[1]; } Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(mProviderId)}, null); if (cursor == null) return; Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( cursor, contentResolver, mProviderId, false, null); if (mUser == null) mUser = makeUser(providerSettings, contentResolver); providerSettings.close(); execute(new Runnable() { @Override public void run() { do_login(); } }); } private void loginSync(long accountId, String passwordTemp, long providerId, boolean retry) { mAccountId = accountId; mPassword = passwordTemp; mProviderId = providerId; mRetryLogin = retry; ContentResolver contentResolver = mContext.getContentResolver(); if (mPassword == null) mPassword = Imps.Account.getPassword(contentResolver, mAccountId); mIsGoogleAuth = false;// mPassword.startsWith(GTalkOAuth2.NAME); if (mIsGoogleAuth) { mPassword = mPassword.split(":")[1]; } Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(mProviderId)}, null); if (cursor == null) return; Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( cursor, contentResolver, mProviderId, false, null); if (mUser == null) mUser = makeUser(providerSettings, contentResolver); providerSettings.close(); do_login(); } // Runs in executor thread private void do_login() { if (getState() == LOGGED_IN || getState() == SUSPENDED || getState() == SUSPENDING ) return; /* if (mConnection != null) { setState(getState(), new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, "still trying...")); return; }*/ ContentResolver contentResolver = mContext.getContentResolver(); Cursor cursor = contentResolver.query(Imps.ProviderSettings.CONTENT_URI, new String[]{Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE}, Imps.ProviderSettings.PROVIDER + "=?", new String[]{Long.toString(mProviderId)}, null); if (cursor == null) return; //not going to work Imps.ProviderSettings.QueryMap providerSettings = new Imps.ProviderSettings.QueryMap( cursor, contentResolver, mProviderId, false, null); // providerSettings is closed in initConnection(); mUsername = Imps.Account.getUserName(contentResolver, mAccountId); String defaultStatus = null; mNeedReconnect = true; setState(LOGGING_IN, null); mUserPresence = new Presence(Presence.AVAILABLE, defaultStatus, Presence.CLIENT_TYPE_MOBILE); try { if (mUsername == null || mUsername.length() == 0) throw new Exception("empty username not allowed"); initConnectionAndLogin(providerSettings, mUsername); setState(LOGGED_IN, null); debug(TAG, "logged in"); mNeedReconnect = false; } catch (XMPPException e) { debug(TAG, "exception thrown on connection",e); ImErrorInfo info = new ImErrorInfo(ImErrorInfo.CANT_CONNECT_TO_SERVER, e.getMessage()); mRetryLogin = true; // our default behavior is to retry if (mConnection != null && mConnection.isConnected() && (!mConnection.isAuthenticated())) { debug(TAG, "not authorized - will not retry"); info = new ImErrorInfo(ImErrorInfo.INVALID_USERNAME, "invalid user/password"); setState(SUSPENDED, info); mRetryLogin = false; mNeedReconnect = false; } if (mRetryLogin && getState() != SUSPENDED) { debug(TAG, "will retry"); setState(LOGGING_IN, info); maybe_reconnect(); } else { //debug(TAG, "will not retry"); //WE MUST ALWAYS RETRY! disconnect(); disconnected(info); } } catch (Exception e) { debug(TAG, "login failed",e); mRetryLogin = true; mNeedReconnect = true; debug(TAG, "will retry"); ImErrorInfo info = new ImErrorInfo(ImErrorInfo.UNKNOWN_ERROR, "keymanagement exception"); setState(LOGGING_IN, info); } finally { providerSettings.close(); if (!cursor.isClosed()) cursor.close(); } } private synchronized Omemo initOmemo (XMPPTCPConnection conn) throws Exception { if (conn != null && conn.isAuthenticated()) { mOmemoInstance = new Omemo(conn, mContext); mOmemoInstance.getManager().addOmemoMessageListener(new OmemoMessageListener() { @Override public void onOmemoMessageReceived(String body, org.jivesoftware.smack.packet.Message message, org.jivesoftware.smack.packet.Message message1, OmemoMessageInformation omemoMessageInformation) { if (body != null) { debug(TAG, "got inbound message omemo: from:" + message.getFrom() + "=" + message.getBody()); message.setBody(body); handleMessage(message, true); } else { debug(TAG, "got empty ibound message omemo: from:" + message.getFrom().toString()); } } }); } return mOmemoInstance; } private synchronized Omemo getOmemo () throws Exception { if (mOmemoInstance == null && mConnection != null) { initOmemo(mConnection); } return mOmemoInstance; } // TODO shouldn't setProxy be handled in Imps/settings? public void setProxy(String type, String host, int port) { if (type == null) { mProxyInfo = null; } else { ProxyInfo.ProxyType pType = ProxyInfo.ProxyType.valueOf(type); String username = null; String password = null; if (type.equals(TorProxyInfo.PROXY_TYPE) //socks5 && host.equals(TorProxyInfo.PROXY_HOST) //127.0.0.1 && port == TorProxyInfo.PROXY_PORT) //9050 { //if the proxy is for Orbot/Tor then generate random usr/pwd to isolate Tor streams if (rndForTorCircuits == null) rndForTorCircuits = new SecureRandom(); username = rndForTorCircuits.nextInt(100000)+""; password = rndForTorCircuits.nextInt(100000)+""; } mProxyInfo = new ProxyInfo(pType, host, port, username, password); } } private void initConnectionAndLogin (Imps.ProviderSettings.QueryMap providerSettings,String userName) throws InterruptedException, IOException, SmackException, XMPPException, KeyManagementException, NoSuchAlgorithmException, IllegalStateException, RuntimeException { Roster.SubscriptionMode subMode = Roster.SubscriptionMode.manual;//Roster.SubscriptionMode.accept_all;//load this from a preference Debug.onConnectionStart(); //only activates if Debug TRUE is set, so you can leave this in! initConnection(providerSettings, userName); //disable compression based on statement by Ge0rg // mConfig.setCompressionEnabled(false); if (mConnection.isConnected() && mConnection.isSecureConnection()) { mResource = providerSettings.getXmppResource(); mRoster = Roster.getInstanceFor(mConnection); mRoster.setRosterLoadedAtLogin(false); mRoster.setSubscriptionMode(subMode); mChatManager = ChatManager.getInstanceFor(mConnection); mPingManager = PingManager.getInstanceFor(mConnection) ; mConnection.login(mUsername, mPassword, Resourcepart.from(mResource)); String fullJid = mConnection.getUser().toString(); XmppAddress xa = new XmppAddress(fullJid); if (mUser == null) mUser = makeUser(providerSettings,mContext.getContentResolver()); mStreamHandler.notifyInitialLogin(); initServiceDiscovery(); sendPresencePacket(); getContactListManager().listenToRoster(mRoster); getContactListManager().loadContactListsAsync(); MultiUserChatManager.getInstanceFor(mConnection).addInvitationListener(new InvitationListener() { @Override public void invitationReceived(XMPPConnection xmppConnection, MultiUserChat muc, EntityJid entityJid, String reason, String password, org.jivesoftware.smack.packet.Message message, MUCUser.Invite invite) { try { getChatGroupManager().acceptInvitationAsync(invite.getFrom().toString()); XmppAddress xa = new XmppAddress(muc.getRoom().toString()); mChatGroupManager.joinChatGroupAsync(xa, reason); ChatSession session = mSessionManager.findSession(muc.getRoom()); //create a session if (session == null) { ImEntity participant = findOrCreateParticipant(xa.getAddress(), true); if (participant != null) session = mSessionManager.createChatSession(participant, false); if (session != null) ((ChatGroup) session.getParticipant()).setName(reason); } } catch (Exception se) { Log.e(TAG,"error accepting invite",se); } } }); execute(new Runnable () { public void run () { sendVCard(); } }); } else { //throw some meaningful error message here throw new SmackException("Unable to securely conenct to server"); } } public void broadcastMigrationIdentity (String newIdentity) { sendVCard(newIdentity); String migrateMessage = mContext.getString(R.string.migrate_message) + ' ' + newIdentity; mUserPresence = new Presence(Presence.AVAILABLE, migrateMessage, Presence.CLIENT_TYPE_MOBILE); sendPresencePacket(); } public void sendVCard () { sendVCard(null); } public void sendVCard (String migrateJabberId) { try { String jid = mUser.getAddress().getBareAddress(); VCardManager vCardManager = VCardManager.getInstanceFor(mConnection); VCard vCard = null; try { vCard = vCardManager.loadVCard(JidCreate.entityBareFrom(jid)); } catch (Exception e){ // debug(TAG,"error loading vcard",e); } boolean setAvatar = true; if (vCard == null) { vCard = new VCard(); vCard.setJabberId(jid); setAvatar = true; } else if (vCard.getAvatarHash() != null) { setAvatar = !DatabaseUtils.doesAvatarHashExist(mContext.getContentResolver(), Imps.Avatars.CONTENT_URI, mUser.getAddress().getBareAddress(), vCard.getAvatarHash()); } vCard.setNickName(mUser.getName()); //if we have moved to a new account, send it here if (migrateJabberId != null) { vCard.setJabberId(migrateJabberId); } if (setAvatar) { byte[] avatar = DatabaseUtils.getAvatarBytesFromAddress(mContext.getContentResolver(), mUser.getAddress().getBareAddress()); if (avatar != null) { vCard.setAvatar(avatar, "image/jpeg"); } } if (mConnection != null && mConnection.isConnected() && mConnection.isAuthenticated()) { debug(TAG, "Saving VCard for: " + mUser.getAddress().getAddress()); vCardManager.saveVCard(vCard); } } catch (Exception e) { debug(TAG,"error saving vcard",e); } } // Runs in executor thread private void initConnection(Imps.ProviderSettings.QueryMap providerSettings, String userName) throws InterruptedException, NoSuchAlgorithmException, KeyManagementException, XMPPException, SmackException, IOException { boolean allowPlainAuth = false;//never! // providerSettings.getAllowPlainAuth(); boolean requireTls = true;// providerSettings.getRequireTls(); //always! boolean doDnsSrv = providerSettings.getDoDnsSrv(); // boolean tlsCertVerify = providerSettings.getTlsCertVerify(); // boolean useSASL = true;//!allowPlainAuth; boolean useTor = providerSettings.getUseTor(); String domain = providerSettings.getDomain(); mPriority = providerSettings.getXmppResourcePrio(); int serverPort = providerSettings.getPort(); String server = providerSettings.getServer(); if ("".equals(server)) server = null; if (domain.equals("dukgo.com")) { doDnsSrv = false; server = "dukgo.com"; } /** * //need to move this to the new NetCipher BroadcastReceiver API try { //if Orbot is on and running, we should use it if (OrbotHelper.isOrbotInstalled(mContext) && OrbotHelper.isOrbotRunning(mContext) && (server != null && (!doDnsSrv))) useTor = true; } catch (Exception e) { debug(TAG,"There was an error checking Orbot: " + e.getMessage()); }*/ debug(TAG, "TLS required? " + requireTls); if (useTor) { setProxy(TorProxyInfo.PROXY_TYPE, TorProxyInfo.PROXY_HOST, TorProxyInfo.PROXY_PORT); } else { setProxy(null, null, -1); } if (mProxyInfo == null) mProxyInfo = null; // If user did not specify a server, and SRV requested then lookup SRV if (doDnsSrv) { //java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); //java.lang.System.setProperty("java.net.preferIPv6Addresses", "false"); debug(TAG, "(DNS SRV) resolving: " + domain); List<HostAddress> listHostsFailed = new ArrayList<>(); List<HostAddress> listHosts = DNSUtil.resolveXMPPServiceDomain(domain, listHostsFailed, ConnectionConfiguration.DnssecMode.disabled); if (listHosts.size() > 0) { server = listHosts.get(0).getFQDN(); serverPort = listHosts.get(0).getPort(); debug(TAG, "(DNS SRV) resolved: " + domain + "=" + server + ":" + serverPort); } } /** if (server != null && server.contains("google.com")) { mUsername = userName + '@' + domain; } else if (domain.contains("gmail.com")) { mUsername = userName + '@' + domain; } else if (mIsGoogleAuth) { mUsername = userName + '@' + domain; } else { mUsername = userName; }**/ if (serverPort == 0) //if serverPort is set to 0 then use 5222 as default serverPort = 5222; mConfig = XMPPTCPConnectionConfiguration.builder(); mConfig.setServiceName(JidCreate.domainBareFrom(domain)); mConfig.setPort(serverPort); mConfig.setCompressionEnabled(true); mConfig.setConnectTimeout(CONNECT_TIMEOUT); // No server requested and SRV lookup wasn't requested or returned nothing - use domain if (server == null) mConfig.setHost(domain); else mConfig.setHost(server); mConfig.setDebuggerEnabled(Debug.DEBUG_ENABLED); //mConfig.setSASLAuthenticationEnabled(useSASL); // Android has no support for Kerberos or GSSAPI, so disable completely SASLAuthentication.unregisterSASLMechanism("KERBEROS_V4"); SASLAuthentication.unregisterSASLMechanism("GSSAPI"); /** SASLAuthentication.registerSASLMechanism( GTalkOAuth2.NAME, GTalkOAuth2.class ); if (mIsGoogleAuth) //if using google auth enable sasl SASLAuthentication.supportSASLMechanism( GTalkOAuth2.NAME, 0); else if (domain.contains("google.com")||domain.contains("gmail.com")) //if not google auth, disable if doing direct google auth SASLAuthentication.unsupportSASLMechanism( GTalkOAuth2.NAME); */ if (allowPlainAuth) SASLAuthentication.unBlacklistSASLMechanism("PLAIN"); SASLAuthentication.unBlacklistSASLMechanism("DIGEST-MD5"); if (mMemTrust == null) mMemTrust = new MemorizingTrustManager(mContext); if (sslContext == null) { sslContext = SSLContext.getInstance(SSLCONTEXT_TYPE); SecureRandom secureRandom = new java.security.SecureRandom(); sslContext.init(null, MemorizingTrustManager.getInstanceList(mContext), secureRandom); if (Build.VERSION.SDK_INT >= 20) { sslContext.getDefaultSSLParameters().setCipherSuites(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES_API_20); } else { sslContext.getDefaultSSLParameters().setCipherSuites(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { mConfig.setKeystoreType("AndroidCAStore"); mConfig.setKeystorePath(null); } else { mConfig.setKeystoreType("BKS"); String path = System.getProperty("javax.net.ssl.trustStore"); if (path == null) path = System.getProperty("java.home") + File.separator + "etc" + File.separator + "security" + File.separator + "cacerts.bks"; mConfig.setKeystorePath(path); } //wait a second while the ssl context init's try { Thread.sleep(1000); } catch (Exception e) {} } int currentapiVersion = android.os.Build.VERSION.SDK_INT; if (currentapiVersion >= 16){ // Enable TLS1.2 and TLS1.1 on supported versions of android // http://stackoverflow.com/questions/16531807/android-client-server-on-tls-v1-2 while (true) { try { mConfig.setEnabledSSLProtocols(new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}); sslContext.getDefaultSSLParameters().setProtocols(new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}); break; } catch (IllegalStateException ise) { try { Thread.sleep(1000); } catch (Exception e){} } } } if (currentapiVersion >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH){ mConfig.setEnabledSSLCiphers(XMPPCertPins.SSL_IDEAL_CIPHER_SUITES); } mConfig.setCustomSSLContext(sslContext); mConfig.setSecurityMode(ConnectionConfiguration.SecurityMode.required); mConfig.setHostnameVerifier( mMemTrust.wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier())); mConfig.setSendPresence(true); XMPPTCPConnection.setUseStreamManagementDefault(true); mConnection = new XMPPTCPConnection(mConfig.build()); //debug(TAG,"is secure connection? " + mConnection.isSecureConnection()); //debug(TAG,"is using TLS? " + mConnection.isUsingTLS()); mConnection.addAsyncStanzaListener(new StanzaListener() { @Override public void processStanza(Stanza stanza) { debug(TAG, "receive message: " + stanza.getFrom() + " to " + stanza.getTo()); org.jivesoftware.smack.packet.Message smackMessage = (org.jivesoftware.smack.packet.Message) stanza; handleMessage(smackMessage, false); String msg_xml = smackMessage.toXML().toString(); try { handleChatState(smackMessage.getFrom().toString(), msg_xml); } catch (RemoteException re) { //no worries } } }, new StanzaTypeFilter(org.jivesoftware.smack.packet.Message.class)); mConnection.addAsyncStanzaListener(new StanzaListener() { @Override public void processStanza(Stanza packet) { org.jivesoftware.smack.packet.Presence presence = (org.jivesoftware.smack.packet.Presence) packet; qPresence.push(presence); } }, new StanzaTypeFilter(org.jivesoftware.smack.packet.Presence.class)); if (mTimerPackets == null) initPacketProcessor(); if (mTimerPresence == null) initPresenceProcessor (); if (mTimerNewContacts == null) initNewContactProcessor(); ConnectionListener connectionListener = new ConnectionListener() { /** * Called from smack when connect() is fully successful * * This is called on the executor thread while we are in reconnect() */ @Override public void reconnectionSuccessful() { if (mStreamHandler == null || !mStreamHandler.isResumePending()) { debug(TAG, "Reconnection success"); onReconnectionSuccessful(); mRoster = Roster.getInstanceFor(mConnection); } else { debug(TAG, "Ignoring reconnection callback due to pending resume"); } } @Override public void reconnectionFailed(Exception e) { // We are not using the reconnection manager // throw new UnsupportedOperationException(); execute(new Runnable() { public void run() { mNeedReconnect = true; setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network error")); reconnect(); } }); } @Override public void reconnectingIn(int seconds) { // // We are not using the reconnection manager // throw new UnsupportedOperationException(); } @Override public void connectionClosedOnError(final Exception e) { /* * This fires when: * - Packet reader or writer detect an error * - Stream compression failed * - TLS fails but is required * - Network error * - We forced a socket shutdown */ debug(TAG, "reconnect on error: " + e.getMessage()); if (e.getMessage().contains("conflict")) { execute(new Runnable() { @Override public void run() { // disconnect(); disconnected(new ImErrorInfo(ImpsErrorInfo.ALREADY_LOGGED, "logged in from another location")); } }); } else if (!mNeedReconnect) { execute(new Runnable() { public void run() { if (getState() == LOGGED_IN) { mNeedReconnect = true; setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network error")); reconnect(); } } }); } } @Override public void connected(XMPPConnection connection) { debug(TAG, "connected"); } @Override public void authenticated(XMPPConnection connection, boolean resumed) { debug(TAG, "authenticated: resumed=" + resumed); try { initOmemo((XMPPTCPConnection)connection); } catch (Exception e) { debug("OMEMO","There was a problem init'g omemo",e); } } @Override public void connectionClosed() { debug(TAG, "connection closed"); /* * This can be called in these cases: * - Connection is shutting down * - because we are calling disconnect * - in do_logout * * - NOT * - because server disconnected "normally" * - we were trying to log in (initConnection), but are failing * - due to network error * - due to login failing */ //if the state is logged in, we should try to reconnect! if (getState() == LOGGED_IN) { execute(new Runnable() { public void run() { mNeedReconnect = true; setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network error")); reconnect(); } }); } } }; mConnection.addConnectionListener(connectionListener); mStreamHandler = new XmppStreamHandler(mConnection, connectionListener); Exception xmppConnectException = null; AbstractXMPPConnection conn = mConnection.connect(); } private void handleMessage (org.jivesoftware.smack.packet.Message smackMessage, boolean encrypted) { String body = smackMessage.getBody(); boolean isGroupMessage = smackMessage.getType() == org.jivesoftware.smack.packet.Message.Type.groupchat; if (smackMessage.getError() != null) { // smackMessage.getError().getCode(); String error = "Error " + smackMessage.getError() + " (" + smackMessage.getError().getCondition() + "): " + smackMessage.getError().getConditionText(); debug(TAG, error); return; } if (body == null) { Collection<org.jivesoftware.smack.packet.Message.Body> mColl = smackMessage.getBodies(); for (org.jivesoftware.smack.packet.Message.Body bodyPart : mColl) { String msg = bodyPart.getMessage(); if (msg != null) { body = msg; break; } } } DeliveryReceipt drIncoming = (DeliveryReceipt) smackMessage.getExtension("received", DeliveryReceipt.NAMESPACE); ChatSession session = findOrCreateSession(smackMessage.getFrom().toString(), isGroupMessage); if (session != null) //not subscribed so don't do anything { if (drIncoming != null) session.onMessageReceipt(drIncoming.getId()); if (body != null && session != null) { Message rec = new Message(body); rec.setTo(new XmppAddress(smackMessage.getTo().toString())); rec.setFrom(new XmppAddress(smackMessage.getFrom().toString())); rec.setDateTime(new Date()); rec.setID(smackMessage.getStanzaId()); if (encrypted) rec.setType(Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED); else rec.setType(Imps.MessageType.INCOMING); // Detect if this was said by us, and mark message as outgoing if (isGroupMessage) { if (TextUtils.isEmpty(rec.getFrom().getResource())) { return; //do nothing if there is no resource since that is a system message } else if (rec.getFrom().getResource().equals(rec.getTo().getUser())) { try { //rec.setType(Imps.MessageType.OUTGOING); Occupant oc = mChatGroupManager.getMultiUserChat(rec.getFrom().getBareAddress()).getOccupant(JidCreate.entityFullFrom(rec.getFrom().getAddress())); if (oc != null && oc.getJid().equals(mUser.getAddress().getAddress())) return; //do nothing if it is from us } catch (Exception e){ debug(TAG,"error parsing address",e); } } } setPresence(smackMessage.getFrom(),Presence.AVAILABLE); boolean good = session.onReceiveMessage(rec); if (smackMessage.getExtension("request", DeliveryReceipt.NAMESPACE) != null) { if (good) { debug(TAG, "sending delivery receipt"); // got XEP-0184 request, send receipt sendReceipt(smackMessage); session.onReceiptsExpected(true); } else { debug(TAG, "not sending delivery receipt due to processing error"); } } else { //no request for delivery receipt session.onReceiptsExpected(false); } } } } private void sendPresencePacket() { qPacket.add(makePresencePacket(mUserPresence)); } private void sendReceipt(org.jivesoftware.smack.packet.Message msg) { debug(TAG, "sending XEP-0184 ack to " + msg.getFrom() + " id=" + msg.getPacketID()); org.jivesoftware.smack.packet.Message ack = new org.jivesoftware.smack.packet.Message( msg.getFrom(), msg.getType()); ack.addExtension(new DeliveryReceipt(msg.getStanzaId())); sendPacket(ack); } protected int parsePresence(org.jivesoftware.smack.packet.Presence presence) { int type = Imps.Presence.AVAILABLE; org.jivesoftware.smack.packet.Presence.Mode rmode = presence.getMode(); org.jivesoftware.smack.packet.Presence.Type rtype = presence.getType(); if (rmode == org.jivesoftware.smack.packet.Presence.Mode.away || rmode == org.jivesoftware.smack.packet.Presence.Mode.xa) type = Imps.Presence.AWAY; else if (rmode == org.jivesoftware.smack.packet.Presence.Mode.dnd) type = Imps.Presence.DO_NOT_DISTURB; else if (rtype == org.jivesoftware.smack.packet.Presence.Type.unavailable) type = Imps.Presence.OFFLINE; else if (rtype == org.jivesoftware.smack.packet.Presence.Type.unsubscribed) type = Imps.Presence.OFFLINE; return type; } // We must release resources here, because we will not be reused void disconnected(ImErrorInfo info) { debug(TAG, "disconnected"); //join(); setState(DISCONNECTED, info); } @Override public void logoutAsync() { new Thread(new Runnable() { @Override public void run() { do_logout(); } }).start(); } // Force immediate logout public void logout() { logoutAsync(); } // Usually runs in executor thread, unless called from logout() private void do_logout() { setState(LOGGING_OUT, null); disconnect(); disconnected(null); } // Runs in executor thread private void disconnect() { clearPing(); try { // mStreamHandler.quickShutdown(); mConnection.disconnect(); } catch (Throwable th) { // ignore } mConnection = null; mNeedReconnect = false; mRetryLogin = false; } @Override public void reestablishSessionAsync(Map<String, String> sessionContext) { execute(new Runnable() { @Override public void run() { if (getState() == SUSPENDED) { debug(TAG, "reestablish"); mNeedReconnect = false; setState(LOGGING_IN, null); maybe_reconnect(); } } }); } @Override public void suspend() { setState(SUSPENDED, null); // Do not try to reconnect anymore if we were asked to suspend mNeedReconnect = false; clearPing(); /** new Thread(new Runnable() { @Override public void run() { debug(TAG, "suspend"); //if (mStreamHandler != null) // mStreamHandler.quickShutdown(); } });**/ } private ChatSession findOrCreateSession(String address, boolean groupChat) { try { ChatSession session = mSessionManager.findSession(JidCreate.bareFrom(address)); Jid jid = JidCreate.from(address); if (jid.hasNoResource()) return null; //create a session if this it not groupchat if (session == null && (!groupChat)) { ImEntity participant = findOrCreateParticipant(address, groupChat); if (participant != null) { session = mSessionManager.createChatSession(participant, false); mContactListManager.refreshPresence(address); /** try { getOmemo().trustOmemoDevice(jid.asBareJid(), true); } catch (Exception ioe) { debug(TAG, "error fetching omemo devices", ioe); } **/ } } return session; } catch (Exception e) { debug(ImApp.LOG_TAG,"error findOrCreateSession",e); return null; } } synchronized ImEntity findOrCreateParticipant(String address, boolean isGroupChat) { ImEntity participant = null; if (isGroupChat) { Address xmppAddress = new XmppAddress(address); participant = mChatGroupManager.getChatGroup(xmppAddress); if (participant == null) { try { mChatGroupManager.createChatGroupAsync(address, xmppAddress.getUser(), mUser.getName()); participant = mChatGroupManager.getChatGroup(xmppAddress); } catch (Exception e) { Log.w(TAG, "unable to join group chat: " + e.toString()); return null; } } } else { return mContactListManager.getContact(address); } return participant; } Contact findOrCreateContact(String address) { return (Contact) findOrCreateParticipant(address, false); } private Contact makeContact(String address) { Contact contact = null; //load from roster if we don't have the contact RosterEntry rEntry = null; try { if (mConnection != null) rEntry = mRoster.getEntry(JidCreate.bareFrom(address)); if (rEntry != null) { XmppAddress xAddress = new XmppAddress(address); String name = rEntry.getName(); if (name == null) name = xAddress.getUser(); contact = new Contact(xAddress, name); } else { XmppAddress xAddress = new XmppAddress(address); contact = new Contact(xAddress, xAddress.getUser()); } } catch (XmppStringprepException xe) { //nothing return null; } return contact; } private final class XmppChatSessionManager extends ChatSessionManager { @Override public void sendMessageAsync(ChatSession session, Message message) { if (getState() != LOGGED_IN) { message.setType(Imps.MessageType.QUEUED); //can't send for now return; } MultiUserChat muc = null; org.jivesoftware.smack.packet.Message msgXmpp = null; try { Jid jidTo = JidCreate.from(message.getTo().getAddress()); if (session.getParticipant() instanceof ChatGroup) { muc = ((XmppChatGroupManager)getChatGroupManager()).getMultiUserChat(message.getTo().getAddress()); msgXmpp = muc.createMessage(); } else { msgXmpp = new org.jivesoftware.smack.packet.Message( jidTo.asBareJid(), org.jivesoftware.smack.packet.Message.Type.chat); } if (message.getFrom() == null) msgXmpp.setFrom(JidCreate.bareFrom(mUser.getAddress().getAddress())); else msgXmpp.setFrom(JidCreate.bareFrom(message.getFrom().getAddress())); msgXmpp.setBody(message.getBody()); if (message.getID() != null) msgXmpp.setStanzaId(message.getID()); else message.setID(msgXmpp.getStanzaId()); Chat thisChat = mChatManager.createChat(jidTo.asEntityJidIfPossible()); //this isn't an OTR message, so let's try OMEMO if (message.getType() == Imps.MessageType.QUEUED) { //if this isn't already OTR encrypted, and the JID can support OMEMO then do it! if (session.canOmemo()) { try { org.jivesoftware.smack.packet.Message msgEncrypted = getOmemo().getManager().encrypt(jidTo.asBareJid(), msgXmpp); msgEncrypted.setStanzaId(msgXmpp.getStanzaId()); msgEncrypted.addExtension(new DeliveryReceiptRequest()); thisChat.sendMessage(msgEncrypted); message.setType(Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED); } catch (CryptoFailedException cfe) { debug(TAG, "crypto failed", cfe); } catch (UndecidedOmemoIdentityException uoie) { debug(TAG, "crypto failed", uoie); //if we are connected, then try again if (mConnection != null && mConnection.isConnected()) { getOmemo().trustOmemoDevice(msgXmpp.getFrom().asBareJid(), true); getOmemo().trustOmemoDevice(jidTo.asBareJid(), true); org.jivesoftware.smack.packet.Message msgEncrypted = getOmemo().getManager().encrypt(jidTo.asBareJid(), msgXmpp); msgEncrypted.addExtension(new DeliveryReceiptRequest()); msgEncrypted.setStanzaId(msgXmpp.getStanzaId()); thisChat.sendMessage(msgEncrypted); message.setType(Imps.MessageType.OUTGOING_ENCRYPTED_VERIFIED); } } return; } else { message.setType(Imps.MessageType.QUEUED); return; } } else { msgXmpp.addExtension(new DeliveryReceiptRequest()); thisChat.sendMessage(msgXmpp); return; } } catch (Exception xe) { debug(TAG, "unable to send message",xe); message.setType(Imps.MessageType.QUEUED); } } ChatSession findSession(Jid jid) { ChatSession result = mSessions.get(jid.toString()); // if (result == null) // result = mSessions.get(XmppAddress.stripResource(address)); return result; } @Override public ChatSession createChatSession(ImEntity participant, boolean isNewSession) { ChatSession session = super.createChatSession(participant,isNewSession); mSessions.put(participant.getAddress().getAddress(),session); return session; } @Override public boolean resourceSupportsOmemo(Jid jid) { try { return getOmemo().resourceSupportsOmemo(jid); } catch (Exception e) { debug("OMEMO","There was a problem checking the resource",e); } return false; } } private void requestPresenceRefresh (String address) { mContactListManager.refreshPresence(address); } public class XmppContactListManager extends ContactListManager { @Override protected void setListNameAsync(final String name, final ContactList list) { execute(new Runnable() { @Override public void run() { do_setListName(name, list); } }); } // Runs in executor thread private void do_setListName(String name, ContactList list) { debug(TAG, "set list name"); //mRoster.getGroup(list.getName()).setName(name); try { mRoster.getGroup(list.getName()).setName(name); notifyContactListNameUpdated(list, name); } catch (Exception e) {} } @Override public String normalizeAddress(String address) { return Address.stripResource(address); } @Override public void loadContactListsAsync() { execute(new Runnable() { @Override public void run() { do_loadContactLists(); } }); } // For testing /* public void loadContactLists() { do_loadContactLists(); }*/ /** * Create new list of contacts from roster entries. * * Runs in executor thread ** * @return contacts from roster which were not present in skiplist. */ /* private Collection<Contact> fillContacts(Collection<RosterEntry> entryIter, Set<String> skipList) { Roster roster = mConnection.getRoster(); Collection<Contact> contacts = new ArrayList<Contact>(); for (RosterEntry entry : entryIter) { String address = entry.getUser(); if (skipList != null && !skipList.add(address)) continue; String name = entry.getName(); if (name == null) name = address; XmppAddress xaddress = new XmppAddress(address); org.jivesoftware.smack.packet.Presence presence = roster.getPresence(address); String status = presence.getStatus(); String resource = null; Presence p = new Presence(parsePresence(presence), status, null, null, Presence.CLIENT_TYPE_DEFAULT); String from = presence.getFrom(); if (from != null && from.lastIndexOf("/") > 0) { resource = from.substring(from.lastIndexOf("/") + 1); if (resource.indexOf('.')!=-1) resource = resource.substring(0,resource.indexOf('.')); p.setResource(resource); } Contact contact = mContactListManager.getContact(xaddress.getBareAddress()); if (contact == null) contact = new Contact(xaddress, name); contact.setPresence(p); contacts.add(contact); } return contacts; } */ // Runs in executor thread private void do_loadContactLists() { debug(TAG, "load contact lists"); //since we don't show lists anymore, let's just load all entries together try { mRoster.reloadAndWait(); } catch (Exception e) { debug(TAG,"error loading roaster",e); return; } LastActivityManager lam = LastActivityManager.getInstanceFor(mConnection); long now = new Date().getTime(); ContactList cl; try { cl = getDefaultContactList(); } catch (ImException e1) { debug(TAG,"couldn't read default list"); cl = null; } if (cl == null) { String generalGroupName = mContext.getString(R.string.buddies); Collection<Contact> contacts = new ArrayList<Contact>(); XmppAddress groupAddress = new XmppAddress(generalGroupName); cl = new ContactList(groupAddress,generalGroupName, true, contacts, this); cl.setDefault(true); mDefaultContactList = cl; notifyContactListCreated(cl); } if (mConnection != null) { for (RosterEntry rEntry : mRoster.getEntries()) { String address = rEntry.getJid().toString(); String name = rEntry.getName(); if (mUser.getAddress().getBareAddress().equals(address)) //don't load a roster for yourself continue; Contact contact = null; contact = getContact(address); if (contact == null) { XmppAddress xAddr = new XmppAddress(address); if (name == null || name.length() == 0) name = xAddr.getUser(); contact = new Contact(xAddr, name); } // if (p != null) // contact.setPresence(p); if (!cl.containsContact(contact)) { try { cl.addExistingContact(contact); } catch (ImException e) { debug(TAG, "could not add contact to list: " + e.getLocalizedMessage()); } } int subStatus = Imps.ContactsColumns.SUBSCRIPTION_STATUS_NONE; // if (rEntry.get ) // subStatus = Imps.ContactsColumns.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING; int subType = 0; if (rEntry.getType() == RosterPacket.ItemType.both) subType = Imps.ContactsColumns.SUBSCRIPTION_TYPE_BOTH; else if (rEntry.getType() == RosterPacket.ItemType.none) subType = Imps.ContactsColumns.SUBSCRIPTION_TYPE_NONE; else if (rEntry.getType() == RosterPacket.ItemType.to) { subType = Imps.ContactsColumns.SUBSCRIPTION_TYPE_TO; } else if (rEntry.getType() == RosterPacket.ItemType.from) { subType = Imps.ContactsColumns.SUBSCRIPTION_TYPE_FROM; } else if (rEntry.getType() == RosterPacket.ItemType.remove) subType = Imps.ContactsColumns.SUBSCRIPTION_TYPE_REMOVE; /** try { LastActivity activity = lam.getLastActivity(JidCreate.from(address)); contact.setPresence(new Date(activity.getIdleTime()); } catch (Exception e) { Log.e("LastActivity","error getting last activity for: " + address,e); }**/ try { mContactListManager.getSubscriptionRequestListener().onSubScriptionChanged(contact, mProviderId, mAccountId, subStatus, subType); } catch (RemoteException re) { } } } notifyContactListLoaded(cl); notifyContactListsLoaded(); } // Runs in executor thread public void addContactsToList(Collection<String> addresses) { debug(TAG, "add contacts to lists"); ContactList cl; try { cl = mContactListManager.getDefaultContactList(); } catch (ImException e1) { debug(TAG,"couldn't read default list"); cl = null; } if (cl == null) { String generalGroupName = mContext.getString(R.string.buddies); Collection<Contact> contacts = new ArrayList<Contact>(); XmppAddress groupAddress = new XmppAddress(generalGroupName); cl = new ContactList(groupAddress,generalGroupName, true, contacts, this); notifyContactListCreated(cl); } for (String address : addresses) { if (mUser.getAddress().getBareAddress().equals(address)) //don't load a roster for yourself continue; Contact contact = getContact(address); if (contact == null) { XmppAddress xAddr = new XmppAddress(address); contact = new Contact(xAddr,xAddr.getUser()); } //org.jivesoftware.smack.packet.Presence p = roster.getPresence(contact.getAddress().getBareAddress()); //qPresence.push(p); if (!cl.containsContact(contact)) { try { cl.addExistingContact(contact); } catch (ImException e) { debug(TAG,"could not add contact to list: " + e.getLocalizedMessage()); } } } notifyContactListLoaded(cl); notifyContactListsLoaded(); } public void refreshPresence (String address) { try { org.jivesoftware.smack.packet.Presence presence = mRoster.getPresence(JidCreate.bareFrom(address)); if (presence != null) { handlePresenceChanged(presence); } } catch (XmppStringprepException xe) { debug(TAG,"error refreshing presence: " + xe,xe); } } /* * 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) { Roster roster = mConnection.getRoster(); //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 = roster.getPresence(address); if (presence != null) { debug(TAG, "processing queued presence: " + address + " - " + presence.getStatus()); unprocdPresence.remove(address); contact.setPresence(new Presence(parsePresence(presence), presence.getStatus(), null, null, Presence.CLIENT_TYPE_DEFAULT)); Contact[] updatedContact = {contact}; notifyContactsPresenceUpdated(updatedContact); } } }*/ public void listenToRoster(final Roster roster) { roster.addRosterListener(rListener); } RosterListener rListener = new RosterListener() { @Override public void presenceChanged(org.jivesoftware.smack.packet.Presence presence) { // qPresence.push(presence); } @Override public void entriesUpdated(Collection<Jid> addresses) { /** for (Jid address :addresses) { requestPresenceRefresh(address.toString()); }**/ } @Override public void entriesDeleted(Collection<Jid> addresses) { ContactList cl; try { cl = mContactListManager.getDefaultContactList(); for (Jid address : addresses) { Contact contact = new Contact(new XmppAddress(address.toString()),address.toString()); mContactListManager.notifyContactListUpdated(cl, ContactListListener.LIST_CONTACT_REMOVED, contact); } } catch (ImException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void entriesAdded(Collection<Jid> addresses) { try { if (mContactListManager.getState() == LISTS_LOADED) { for (Jid address : addresses) { String addressString = address.toString(); Contact contact = getContact(addressString); if (contact == null) { XmppAddress xAddr = new XmppAddress(addressString); contact = new Contact(xAddr,xAddr.getUser()); } try { ContactList cl = mContactListManager.getDefaultContactList(); if (!cl.containsContact(contact)) cl.addExistingContact(contact); } catch (Exception e) { debug(TAG,"could not add contact to list: " + e.getLocalizedMessage()); } } } } catch (Exception e) { Log.d(TAG,"error adding contacts",e); } } }; @Override protected ImConnection getConnection() { return XmppConnection.this; } @Override protected void doRemoveContactFromListAsync(Contact contact, ContactList list) { // FIXME synchronize this to executor thread if (mConnection == null) return; String address = contact.getAddress().getAddress(); //otherwise, send unsub message and delete from local contact database org.jivesoftware.smack.packet.Presence presence = new org.jivesoftware.smack.packet.Presence( org.jivesoftware.smack.packet.Presence.Type.unsubscribe); presence.setTo(address); sendPacket(presence); presence = new org.jivesoftware.smack.packet.Presence( org.jivesoftware.smack.packet.Presence.Type.unsubscribed); presence.setTo(address); sendPacket(presence); try { RosterEntry entry = mRoster.getEntry(JidCreate.bareFrom(address)); RosterGroup group = mRoster.getGroup(list.getName()); if (group == null) { debug(TAG, "could not find group " + list.getName() + " in roster"); if (mRoster != null) mRoster.removeEntry(entry); } else { group.removeEntry(entry); entry = mRoster.getEntry(JidCreate.bareFrom(address)); // Remove from Roster if this is the last group if (entry != null && entry.getGroups().size() <= 1) mRoster.removeEntry(entry); } } catch (Exception e) { debug(TAG, "remove entry failed: " + e.getMessage()); throw new RuntimeException(e); } notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_REMOVED, contact); } @Override protected void doDeleteContactListAsync(ContactList list) { // TODO delete contact list debug(TAG, "delete contact list " + list.getName()); } @Override protected void doCreateContactListAsync(String name, Collection<Contact> contacts, boolean isDefault) { debug(TAG, "create contact list " + name + " default " + isDefault); } @Override protected void doBlockContactAsync(String address, boolean block) { blockContact(address, block); } @Override protected void doAddContactToListAsync(Contact contact, ContactList list, boolean autoSubscribedPresence) throws ImException { debug(TAG, "add contact to " + list.getName()); if (contact.getPresence() == null) { Presence p = new Presence(); contact.setPresence(p); } if (!list.containsContact(contact)) { try { list.addExistingContact(contact); } catch (ImException e) { debug(TAG, "could not add contact to list: " + e.getLocalizedMessage()); } } ChatSession session = findOrCreateSession(contact.getAddress().toString(), false); if (session != null) session.setSubscribed(autoSubscribedPresence); notifyContactListUpdated(list, ContactListListener.LIST_CONTACT_ADDED, contact); Contact[] contacts = {contact}; notifyContactsPresenceUpdated(contacts); if (autoSubscribedPresence) qNewContact.push(contact); } public boolean blockContact(String blockContact, boolean doBlock) { PrivacyItem item=new PrivacyItem(PrivacyItem.Type.jid,blockContact, false, 7); PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(mConnection); if (privacyManager != null) { List<PrivacyItem> list = new ArrayList<PrivacyItem>(); list.add(item); try { privacyManager.updatePrivacyList(PRIVACY_LIST_DEFAULT, list); privacyManager.setActiveListName(PRIVACY_LIST_DEFAULT); return true; } catch (InterruptedException | SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException e) { e.printStackTrace(); return false; } } return false; } @Override public void declineSubscriptionRequest(Contact contact) { debug(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.getAddress().getBareAddress()); sendPacket(response); try { mRoster.reload(); } catch (Exception e){} try { mContactListManager.getSubscriptionRequestListener().onSubscriptionDeclined(contact, mProviderId, mAccountId); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } ChatSession session = findOrCreateSession(contact.getAddress().toString(), false); if (session != null) session.setSubscribed(false); } @Override public void approveSubscriptionRequest(final Contact contact) { org.jivesoftware.smack.packet.Presence response = new org.jivesoftware.smack.packet.Presence( org.jivesoftware.smack.packet.Presence.Type.subscribed); response.setTo(contact.getAddress().getBareAddress()); sendPacket(response); qNewContact.push(contact); // try { mRoster.reload(); } // catch (Exception e){} try { //doAddContactToListAsync(contact, getContactListManager().getDefaultContactList()); mContactListManager.getSubscriptionRequestListener().onSubscriptionApproved(contact, mProviderId, mAccountId); } catch (RemoteException e) { debug (TAG, "error responding to subscription approval: " + e.getLocalizedMessage()); } ChatSession session = findOrCreateSession(contact.getAddress().toString(), false); if (session != null) session.setSubscribed(true); sendPresencePacket(); requestPresenceRefresh(contact.getAddress().getBareAddress()); qAvatar.push(contact.getAddress().getAddress()); } @Override public Contact[] createTemporaryContacts(String[] addresses) { // debug(TAG, "create temporary " + address); Contact[] contacts = new Contact[addresses.length]; int i = 0; for (String address : addresses) { contacts[i++] = makeContact(address); } notifyContactsPresenceUpdated(contacts); return contacts; } @Override protected void doSetContactName(String address, String name) throws ImException { // set name try { RosterEntry entry = mRoster.getEntry(JidCreate.bareFrom(address)); // confirm entry still exists if (entry == null) { return; } entry.setName(name); } catch (Exception e) { throw new ImException(e.toString()); } } } public void sendHeartbeat(final long heartbeatInterval) { // Don't let heartbeats queue up if we have long running tasks - only // do the heartbeat if executor is idle. boolean success = executeIfIdle(new Runnable() { @Override public void run() { debug(TAG, "heartbeat state = " + getState()); doHeartbeat(heartbeatInterval); } }); if (!success) { debug(TAG, "failed to schedule heartbeat state = " + getState()); } } // Runs in executor thread public void doHeartbeat(long heartbeatInterval) { heartbeatSequence++; if (getState() == SUSPENDED) { debug(TAG, "heartbeat during suspend"); return; } if (mConnection == null && mRetryLogin) { debug(TAG, "reconnect with login"); do_login(); return; } if (mConnection == null) return; if (mNeedReconnect) { reconnect(); } else if (!mConnection.isConnected() && getState() == LOGGED_IN) { // Smack failed to tell us about a disconnect debug(TAG, "reconnect on unreported state change"); setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network disconnected")); force_reconnect(); } else if (getState() == LOGGED_IN) { if (PING_ENABLED) { // Check ping on every heartbeat. checkPing() will return true immediately if we already checked. if (!mPingSuccess) { debug(TAG, "reconnect on ping failed: " + mUser.getAddress().getAddress()); setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "network timeout")); maybe_reconnect(); } else { // Send pings only at intervals configured by the user if (heartbeatSequence >= heartbeatInterval) { heartbeatSequence = 0; debug(TAG, "ping"); sendPing(); } } } } } private void clearPing() { debug(TAG, "clear ping"); heartbeatSequence = 0; } boolean mPingSuccess = true; // Runs in executor thread private void sendPing() { try { mPingSuccess = mPingManager.pingMyServer() ; ;} catch (Exception e) { mPingSuccess = false; } } @Override public void networkTypeChanged() { super.networkTypeChanged(); execute(new Runnable() { @Override public void run() { if (mState == SUSPENDED || mState == SUSPENDING) { debug(TAG, "network type changed"); mNeedReconnect = false; setState(LOGGING_IN, null); reconnect(); } } }); } /* * Force a shutdown and reconnect, unless we are already reconnecting. * * Runs in executor thread */ private void force_reconnect() { debug(TAG, "force_reconnect mNeedReconnect=" + mNeedReconnect + " state=" + getState() + " connection?=" + (mConnection != null)); if (mConnection == null) return; if (mNeedReconnect) return; mNeedReconnect = true; try { if (mConnection != null && mConnection.isConnected()) { mStreamHandler.quickShutdown(); } } catch (Exception e) { Log.w(TAG, "problem disconnecting on force_reconnect: " + e.getMessage()); } reconnect(); } /* * Reconnect unless we are already in the process of doing so. * * Runs in executor thread. */ private void maybe_reconnect() { debug(TAG, "maybe_reconnect mNeedReconnect=" + mNeedReconnect + " state=" + getState() + " connection?=" + (mConnection != null)); // This is checking whether we are already in the process of reconnecting. If we are, // doHeartbeat will take care of reconnecting. if (mNeedReconnect) return; if (getState() == SUSPENDED) return; if (mConnection == null) return; mNeedReconnect = true; reconnect(); } /* * Retry connecting * * Runs in executor thread */ private void reconnect() { if (getState() == SUSPENDED) { debug(TAG, "reconnect during suspend, ignoring"); return; } if (mConnection != null) { // It is safe to ask mConnection whether it is connected, because either: // - We detected an error using ping and called force_reconnect, which did a shutdown // - Smack detected an error, so it knows it is not connected // so there are no cases where mConnection can be confused about being connected here. // The only left over cases are reconnect() being called too many times due to errors // reported multiple times or errors reported during a forced reconnect. // The analysis above is incorrect in the case where Smack loses connectivity // while trying to log in. This case is handled in a future heartbeat // by checking ping responses. clearPing(); if (mConnection.isConnected()) { debug(TAG,"reconnect while already connected, assuming good: " + mConnection); mNeedReconnect = false; setState(LOGGED_IN, null); return; } debug(TAG, "reconnect"); try { if (mStreamHandler.isResumePossible()) { // Connect without binding, will automatically trigger a resume debug(TAG, "mStreamHandler resume"); mConnection.connect(); initServiceDiscovery(); } else { debug(TAG, "reconnection on network change failed: " + mUser.getAddress().getAddress()); mConnection = null; mNeedReconnect = true; setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, null)); while (mNeedReconnect) { do_login(); if (mNeedReconnect) try { Thread.sleep(3000);} catch (Exception e){} } } } catch (Exception e) { if (mStreamHandler != null) mStreamHandler.quickShutdown(); mConnection = null; debug(TAG, "reconnection attempt failed", e); // Smack incorrectly notified us that reconnection was successful, reset in case it fails mNeedReconnect = false; setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, e.getMessage())); //while (mNeedReconnect) // do_login(); } } else { mNeedReconnect = false; mConnection = null; debug(TAG, "reconnection on network change failed"); setState(LOGGING_IN, new ImErrorInfo(ImErrorInfo.NETWORK_ERROR, "reconnection on network change failed")); while (mNeedReconnect) do_login(); } } @Override protected void setState(int state, ImErrorInfo error) { debug(TAG, "setState to " + state); super.setState(state, error); if (state == LOGGED_IN) { //update and send new presence packet out mUserPresence = new Presence(Presence.AVAILABLE, "", Presence.CLIENT_TYPE_MOBILE); sendPresencePacket(); mChatGroupManager.reconnectAll(); } } public void debug(String tag, String msg) { // if (Log.isLoggable(TAG, Log.DEBUG)) { if (Debug.DEBUG_ENABLED) { Log.d(tag, "" + mGlobalId + " : " + msg); } } public void debug(String tag, String msg, Exception e) { if (Debug.DEBUG_ENABLED) { Log.e(tag, "" + mGlobalId + " : " + msg, e); } } /* @Override public void handle(Callback[] arg0) throws IOException { for (Callback cb : arg0) { debug(TAG, cb.toString()); } }*/ /* public class MySASLDigestMD5Mechanism extends SASLMechanism { public MySASLDigestMD5Mechanism(SASLAuthentication saslAuthentication) { super(saslAuthentication); } protected void authenticate() throws IOException, XMPPException { String mechanisms[] = { getName() }; java.util.Map props = new HashMap(); sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props, this); super.authenticate(); } public void authenticate(String username, String host, String password) throws IOException, XMPPException { authenticationId = username; this.password = password; hostname = host; String mechanisms[] = { getName() }; java.util.Map props = new HashMap(); sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this); super.authenticate(); } public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { String mechanisms[] = { getName() }; java.util.Map props = new HashMap(); sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh); super.authenticate(); } protected String getName() { return "DIGEST-MD5"; } public void challengeReceived(String challenge) throws IOException { //StringBuilder stanza = new StringBuilder(); byte response[]; if(challenge != null) response = sc.evaluateChallenge(Base64.decode(challenge)); else //response = sc.evaluateChallenge(null); response = sc.evaluateChallenge(new byte[0]); //String authenticationText = ""; Packet responseStanza; //if(response != null) //{ //authenticationText = Base64.encodeBytes(response, 8); //if(authenticationText.equals("")) //authenticationText = "="; if (response == null){ responseStanza = new Response(); } else { responseStanza = new Response(Base64.encodeBytes(response,Base64.DONT_BREAK_LINES)); } //} //stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"); //stanza.append(authenticationText); //stanza.append("</response>"); //getSASLAuthentication().send(stanza.toString()); getSASLAuthentication().send(responseStanza); } } */ private void initServiceDiscovery() { debug(TAG, "init service discovery"); // register connection features ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mConnection); if (!sdm.includesFeature(DISCO_FEATURE)) sdm.addFeature(DISCO_FEATURE); if (!sdm.includesFeature(DeliveryReceipt.NAMESPACE)) sdm.addFeature(DeliveryReceipt.NAMESPACE); DeliveryReceiptManager.getInstanceFor(mConnection).dontAutoAddDeliveryReceiptRequests(); DeliveryReceiptManager.getInstanceFor(mConnection).setAutoReceiptMode(DeliveryReceiptManager.AutoReceiptMode.disabled); } private void onReconnectionSuccessful() { mNeedReconnect = false; setState(LOGGED_IN, null); } public static void addProviderManagerExtensions(){ ProviderManager.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); ProviderManager.addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); ProviderManager.addIQProvider("query","http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); ProviderManager.addIQProvider("query","http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Time try { ProviderManager.addIQProvider("query", "jabber:iq:time", Class.forName("org.jivesoftware.smackx.packet.Time")); } catch (ClassNotFoundException e) { Log.w("TestClient", "Can't load class for org.jivesoftware.smackx.packet.Time"); } //Pings ProviderManager.addIQProvider("ping","urn:xmpp:ping",new PingProvider()); // Roster Exchange // providerManager.addExtensionProvider("x", "jabber:x:roster", new RosterExchangeProvider()); ProviderManager.addIQProvider("vCard", "vcard-temp", new VCardProvider()); // Message Events // providerManager.addExtensionProvider("x", "jabber:x:event", // new MessageEventProvider()); // XHTML ProviderManager.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); // Group Chat Invitations ProviderManager.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider()); // Service Discovery # Items ProviderManager.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info ProviderManager.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Data Forms ProviderManager.addExtensionProvider("x", "jabber:x:data", new DataFormProvider()); // MUC User ProviderManager.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC Admin ProviderManager.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); // MUC Owner ProviderManager.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); // Delayed Delivery //ProviderManager.addExtensionProvider("x", "jabber:x:delay", // new DelayInformationProvider()); // Version try { ProviderManager.addIQProvider("query", "jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); } catch (ClassNotFoundException e) { // Not sure what's happening here. } // VCard ProviderManager.addIQProvider("vCard", "vcard-temp", new VCardProvider()); // Offline Message Requests ProviderManager.addIQProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); // Offline Message Indicator ProviderManager.addExtensionProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); // Last Activity ProviderManager.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider()); // User Search ProviderManager.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo ProviderManager.addIQProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing ProviderManager.addExtensionProvider("addresses", "http://jabber.org/protocol/address", new MultipleAddressesProvider()); // FileTransfer ProviderManager.addIQProvider("si", "http://jabber.org/protocol/si", new StreamInitiationProvider()); // Privacy ProviderManager.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider()); ProviderManager.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider()); ProviderManager.addExtensionProvider("malformed-action", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.MalformedActionError()); ProviderManager.addExtensionProvider("bad-locale", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadLocaleError()); ProviderManager.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadPayloadError()); ProviderManager.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadSessionIDError()); ProviderManager.addExtensionProvider("session-expired", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.SessionExpiredError()); ProviderManager.addIQProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); // Offline Message Indicator ProviderManager.addExtensionProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); ProviderManager.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); ProviderManager.addExtensionProvider("x", "jabber:x:data", new DataFormProvider()); // pm.addExtensionProvider("status ","", new XMLPlayerList()); ProviderManager.addIQProvider("query", "http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); ProviderManager.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); ProviderManager.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Private Data Storage // ProviderManager.addIQProvider("query","jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); // Time /** try { ProviderManager.addIQProvider("query","jabber:iq:time", Class.forName("org.jivesoftware.smackx.packet.Time")); } catch (ClassNotFoundException e) { Log.w("TestClient", "Can't load class for org.jivesoftware.smackx.packet.Time"); }*/ // Roster Exchange // ProviderManager.addExtensionProvider("x","jabber:x:roster", new RosterExchangeProvider()); // Message Events // ProviderManager.addExtensionProvider("x","jabber:x:event", new MessageEventProvider()); // Chat State ChatStateExtensionProvider csep = new ChatStateExtensionProvider(); ProviderManager.addExtensionProvider("active","http://jabber.org/protocol/chatstates", csep); ProviderManager.addExtensionProvider("composing","http://jabber.org/protocol/chatstates", csep); ProviderManager.addExtensionProvider("paused","http://jabber.org/protocol/chatstates", csep); ProviderManager.addExtensionProvider("inactive","http://jabber.org/protocol/chatstates", csep); ProviderManager.addExtensionProvider("gone","http://jabber.org/protocol/chatstates", csep); // XHTML // ProviderManager.addExtensionProvider("html","http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); // Group Chat Invitations // ProviderManager.addExtensionProvider("x","jabber:x:conference", new GroupChatInvitation.Provider()); // Service Discovery # Items // ProviderManager.addIQProvider("query","http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info // ProviderManager.addIQProvider("query","http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Data Forms // ProviderManager.addExtensionProvider("x","jabber:x:data", new DataFormProvider()); // MUC User // ProviderManager.addExtensionProvider("x","http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC Admin // ProviderManager.addIQProvider("query","http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); // MUC Owner //ProviderManager.addIQProvider("query","http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); // Delayed Delivery // ProviderManager.addExtensionProvider("x","jabber:x:delay", new DelayInformationProvider()); // Version /** try { ProviderManager.addIQProvider("query","jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); } catch (ClassNotFoundException err) { // Not sure what's happening here. } **/ // VCard // ProviderManager.addIQProvider("vCard","vcard-temp", new VCardProvider()); // Offline Message Requests //ProviderManager.addIQProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); // Offline Message Indicator //ProviderManager.addExtensionProvider("offline","http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); // Last Activity //ProviderManager.addIQProvider("query","jabber:iq:last", new LastActivity.Provider()); // User Search //ProviderManager.addIQProvider("query","jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo //ProviderManager.addIQProvider("sharedgroup","http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing //ProviderManager.addExtensionProvider("addresses","http://jabber.org/protocol/address", new MultipleAddressesProvider()); // FileTransfer //ProviderManager.addIQProvider("si","http://jabber.org/protocol/si", new StreamInitiationProvider()); //ProviderManager.addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); // Privacy /** ProviderManager.addIQProvider("query","jabber:iq:privacy", new PrivacyProvider()); ProviderManager.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider()); ProviderManager.addExtensionProvider("malformed-action", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.MalformedActionError()); ProviderManager.addExtensionProvider("bad-locale", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadLocaleError()); ProviderManager.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadPayloadError()); ProviderManager.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadSessionIDError()); ProviderManager.addExtensionProvider("session-expired", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.SessionExpiredError()); **/ } class NameSpace { public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info"; public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; public static final String IQ_GATEWAY = "jabber:iq:gateway"; public static final String IQ_GATEWAY_REGISTER = "jabber:iq:gateway:register"; public static final String IQ_LAST = "jabber:iq:last"; public static final String IQ_REGISTER = "jabber:iq:register"; public static final String IQ_REGISTERED = "jabber:iq:registered"; public static final String IQ_ROSTER = "jabber:iq:roster"; public static final String IQ_VERSION = "jabber:iq:version"; public static final String CHATSTATES = "http://jabber.org/protocol/chatstates"; public static final String XEVENT = "jabber:x:event"; public static final String XDATA = "jabber:x:data"; public static final String MUC = "http://jabber.org/protocol/muc"; public static final String MUC_USER = MUC + "#user"; public static final String MUC_ADMIN = MUC + "#admin"; public static final String SPARKNS = "http://www.jivesoftware.com/spark"; public static final String DELAY = "urn:xmpp:delay"; public static final String OFFLINE = "http://jabber.org/protocol/offline"; public static final String X_DELAY = "jabber:x:delay"; public static final String VCARD_TEMP = "vcard-temp"; public static final String VCARD_TEMP_X_UPDATE = "vcard-temp:x:update"; public static final String ATTENTIONNS = "urn:xmpp:attention:0"; } public boolean registerAccount (Imps.ProviderSettings.QueryMap providerSettings, String username, String password, Map<String,String> params) throws Exception { initConnection(providerSettings, username); if (mConnection.isConnected() && mConnection.isSecureConnection()) { org.jivesoftware.smackx.iqregister.AccountManager aMgr = org.jivesoftware.smackx.iqregister.AccountManager.getInstance(mConnection); if (aMgr.supportsAccountCreation()) { aMgr.createAccount(Localpart.from(username), password, params); return true; } } return false; } public boolean changeServerPassword (long providerId, long accountId, String oldPassword, String newPassword) throws Exception { boolean result = false; try { loginSync(accountId, oldPassword, providerId, false); if (mConnection.isConnected() && mConnection.isSecureConnection() && mConnection.isAuthenticated()) { org.jivesoftware.smackx.iqregister.AccountManager aMgr = org.jivesoftware.smackx.iqregister.AccountManager.getInstance(mConnection); aMgr.changePassword(newPassword); result = true; do_logout(); } } catch (XMPPException xe) { result = false; } return result; } private void handleChatState (String from, String chatStateXml) throws RemoteException { Presence p = null; Contact contact = mContactListManager.getContact(from); if (contact == null) return; boolean isTyping = false; //handle is-typing, probably some indication on screen if (chatStateXml.contains(ChatState.active.toString())) { p = new Presence(Presence.AVAILABLE, "", null, null, Presence.CLIENT_TYPE_MOBILE); } else if (chatStateXml.contains(ChatState.composing.toString())) { p = new Presence(Presence.AVAILABLE, "", null, null, Presence.CLIENT_TYPE_MOBILE); isTyping = true; } else if (chatStateXml.contains(ChatState.inactive.toString())||chatStateXml.contains(ChatState.paused.toString())) { } else if (chatStateXml.contains(ChatState.gone.toString())) { } IChatSession csa = mSessionManager.getAdapter().getChatSession(from); if (csa != null) csa.setContactTyping(contact, isTyping); if (p != null) { String[] presenceParts = from.split("/"); if (presenceParts.length > 1) p.setResource(presenceParts[1]); contact.setPresence(p); Collection<Contact> contactsUpdate = new ArrayList<Contact>(); contactsUpdate.add(contact); mContactListManager.notifyContactsPresenceUpdated(contactsUpdate.toArray(new Contact[contactsUpdate.size()])); } } @Override public void sendTypingStatus (final String to, final boolean isTyping) { mExecutor.execute(new Runnable() { public void run() { sendChatState(to, isTyping ? ChatState.composing : ChatState.inactive); } }); } private void sendChatState (String to, ChatState currentChatState) { try { if (mConnection.isConnected()) { findOrCreateSession(to, false); Chat thisChat = mChatManager.createChat(JidCreate.from(to).asEntityJidIfPossible()); ChatStateManager.getInstance(mConnection).setCurrentState(currentChatState, thisChat); } } catch (Exception e) { Log.w(ImApp.LOG_TAG,"error sending chat state: " + e.getMessage()); } } private void setPresence (Jid from, int presenceType) { Presence p = null; Contact contact = mContactListManager.getContact(from.asBareJid().toString()); if (contact == null) return; p = new Presence(presenceType, "", null, null, Presence.CLIENT_TYPE_MOBILE); if (from.hasResource()) p.setResource(from.getResourceOrEmpty().toString()); contact.setPresence(p); Collection<Contact> contactsUpdate = new ArrayList<Contact>(); contactsUpdate.add(contact); mContactListManager.notifyContactsPresenceUpdated(contactsUpdate.toArray(new Contact[contactsUpdate.size()])); } private Contact handlePresenceChanged(org.jivesoftware.smack.packet.Presence presence) { if (presence == null) //our presence isn't really valid return null; if (TextUtils.isEmpty(presence.getFrom())) return null; if (presence.getType() == org.jivesoftware.smack.packet.Presence.Type.error) return null; if (presence.getFrom().toString().startsWith(mUser.getAddress().getBareAddress())) //ignore presence from yourself return null; XmppAddress xaddress = new XmppAddress(presence.getFrom().toString()); Presence p = new Presence(parsePresence(presence), presence.getStatus(), null, null, Presence.CLIENT_TYPE_MOBILE,null,xaddress.getResource()); //this is only persisted in memory p.setPriority(presence.getPriority()); // Get presence from the Roster to handle priorities and such // TODO: this causes bad network and performance issues // if (presence.getType() == Type.available) //get the latest presence for the highest priority if (mContactListManager == null) return null; //we may have logged out Contact contact = mContactListManager.getContact(xaddress.getBareAddress()); if (presence.getFrom().hasResource()) p.setResource(presence.getFrom().getResourceOrEmpty().toString()); if (presence.getType() == org.jivesoftware.smack.packet.Presence.Type.subscribe ) { debug(TAG, "got subscribe request: " + presence.getFrom()); try { ContactList cList = getContactListManager().getDefaultContactList(); if (contact == null) { XmppAddress xAddr = new XmppAddress(presence.getFrom().toString()); contact = new Contact(xAddr, xAddr.getUser()); } mContactListManager.doAddContactToListAsync(contact, cList, false); mContactListManager.getSubscriptionRequestListener().onSubScriptionRequest(contact, mProviderId, mAccountId); contact.setPresence(p); } catch (Exception e) { Log.e(TAG,"remote exception on subscription handling",e); } } else if (presence.getType() == org.jivesoftware.smack.packet.Presence.Type.subscribed) { debug(TAG, "got subscribed confirmation: " + presence.getFrom()); try { if (contact == null) { XmppAddress xAddr = new XmppAddress(presence.getFrom().toString()); contact = new Contact(xAddr, xAddr.getUser()); } mContactListManager.doAddContactToListAsync(contact,getContactListManager().getDefaultContactList(),true); mContactListManager.getSubscriptionRequestListener().onSubscriptionApproved(contact, mProviderId, mAccountId); p.setPriority(1000);//max this out to ensure the user shows as online contact.setPresence(p); // mContactListManager.approveSubscriptionRequest(contact); } catch (Exception e) { Log.e(TAG,"remote exception on subscription handling",e); } } else if (presence.getType() == org.jivesoftware.smack.packet.Presence.Type.unsubscribe) { debug(TAG,"got unsubscribe request: " + presence.getFrom()); //TBD how to handle this // mContactListManager.getSubscriptionRequestListener().onUnSubScriptionRequest(contact); } else if (presence.getType() == org.jivesoftware.smack.packet.Presence.Type.unsubscribed) { debug(TAG,"got unsubscribe request: " + presence.getFrom()); try { mContactListManager.getSubscriptionRequestListener().onSubscriptionDeclined(contact, mProviderId, mAccountId); } catch (RemoteException e) { Log.e(TAG,"remote exception on subscription handling",e); } } //this is typical presence, let's get the latest/highest priority debug(TAG,"got presence: " + presence.getFrom() + "=" + presence.getType()); if (contact != null) { if (contact.getPresence() != null) { Presence pOld = contact.getPresence(); if (pOld == null || pOld.getResource() == null) { contact.setPresence(p); } else if (pOld.getResource() != null && pOld.getResource().equals(p.getResource())) //if the same resource as the existing one, then update it { contact.setPresence(p); } else if (p.getPriority() >= pOld.getPriority()) //if priority is higher, then override { contact.setPresence(p); } } else contact.setPresence(p); ExtensionElement packetExtension=presence.getExtension("x","vcard-temp:x:update"); if (packetExtension != null) { StandardExtensionElement o=(StandardExtensionElement)packetExtension; String hash=o.getAttributeValue("photo"); if (hash != null) { boolean hasMatches = DatabaseUtils.doesAvatarHashExist(mContext.getContentResolver(), Imps.Avatars.CONTENT_URI, contact.getAddress().getBareAddress(), hash); if (!hasMatches) //we must reload qAvatar.push(contact.getAddress().getAddress()); } } } return contact; } private void initPresenceProcessor () { mTimerPresence = new Timer(); mTimerPresence.schedule(new TimerTask() { public void run() { if (qPresence.size() > 0) { try { ContactList cList = getContactListManager().getDefaultContactList(); if (cList == null) return; //not ready yet } catch (Exception e){ //not ready yet return; } Map<String, Contact> alUpdate = new HashMap<String, Contact>(); org.jivesoftware.smack.packet.Presence p = null; Contact contact = null; final int maxBatch = 20; while (qPresence.peek() != null && alUpdate.size()<maxBatch) { p = qPresence.poll(); contact = handlePresenceChanged(p); if (contact != null) { alUpdate.put(contact.getAddress().getBareAddress(),contact); } } if (alUpdate.size() > 0) { loadVCardsAsync(); //Log.d(TAG,"XMPP processed presence q=" + alUpdate.size()); Collection<Contact> contactsUpdate = alUpdate.values(); if (mContactListManager != null) mContactListManager.notifyContactsPresenceUpdated(contactsUpdate.toArray(new Contact[contactsUpdate.size()])); } } } }, 500, 500); } Timer mTimerPackets = null; private void initPacketProcessor () { mTimerPackets = new Timer(); mTimerPackets.scheduleAtFixedRate(new TimerTask() { public void run() { try { org.jivesoftware.smack.packet.Stanza packet = null; if (qPacket.size() > 0) while (qPacket.peek()!=null) { packet = qPacket.poll(); if (mConnection == null || (!mConnection.isConnected())) { debug(TAG, "postponed packet to " + packet.getTo() + " because we are not connected"); postpone(packet); qPacket.push(packet);//return the packet to the stack return; } try { mConnection.sendStanza(packet); } catch (IllegalStateException ex) { postpone(packet); debug(TAG, "postponed packet to " + packet.getTo() + " because socket is disconnected"); qPacket.push(packet);//return the packet to the stack return; } } } catch (Exception e) { Log.e(TAG,"error sending packet",e); } } }, 500, 500); } Timer mTimerNewContacts = null; private void initNewContactProcessor () { mTimerNewContacts = new Timer(); mTimerNewContacts.scheduleAtFixedRate(new TimerTask() { public void run() { try { Contact contact = null; if (qNewContact.size() > 0) while (qNewContact.peek()!=null) { contact = qNewContact.poll(); if (mConnection == null || (!mConnection.isConnected())) { debug(TAG, "postponed adding new contact" + " because we are not connected"); qNewContact.push(contact);//return the packet to the stack return; } else { try { RosterEntry rEntry; ContactList list = mContactListManager.getDefaultContactList(); String[] groups = new String[] { list.getName() }; BareJid jid = JidCreate.bareFrom(contact.getAddress().getBareAddress()); rEntry = mRoster.getEntry(jid); RosterGroup rGroup = mRoster.getGroup(list.getName()); if (rGroup == null) { if (rEntry == null) { mRoster.createEntry(jid, contact.getName(), groups); } } else if (rEntry == null) { mRoster.createEntry(jid, contact.getName(), groups); } } catch (XMPPException e) { debug(TAG,"error updating remote roster",e); qNewContact.push(contact); //try again later } catch (Exception e) { String msg = "Not logged in to server while updating remote roster"; debug(TAG, msg, e); qNewContact.push(contact); //try again later } } } } catch (Exception e) { Log.e(TAG,"error sending packet",e); } } }, 500, 500); } @Override public List getFingerprints (String address) { try { return getOmemo().getFingerprints(JidCreate.bareFrom(address), false); } catch (Exception xe) { return null; } } }