Java tutorial
/* * XMPPService.java * * Tigase Android Messenger * Copyright (C) 2011-2016 "Tigase, Inc." <office@tigase.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. */ package org.tigase.messenger.phone.pro.service; import android.accounts.Account; import android.accounts.AccountManager; import android.app.*; import android.content.*; import android.content.Context; import android.content.pm.PackageInfo; import android.database.Cursor; import android.net.*; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; import org.tigase.messenger.jaxmpp.android.caps.CapabilitiesDBCache; import org.tigase.messenger.jaxmpp.android.chat.AndroidChatManager; import org.tigase.messenger.jaxmpp.android.chat.MarkAsRead; import org.tigase.messenger.jaxmpp.android.muc.AndroidRoomsManager; import org.tigase.messenger.jaxmpp.android.roster.AndroidRosterStore; import org.tigase.messenger.phone.pro.R; import org.tigase.messenger.phone.pro.account.AccountsConstants; import org.tigase.messenger.phone.pro.account.Authenticator; import org.tigase.messenger.phone.pro.account.LoginActivity; import org.tigase.messenger.phone.pro.conversations.chat.ChatActivity; import org.tigase.messenger.phone.pro.conversations.muc.MucActivity; import org.tigase.messenger.phone.pro.db.CPresence; import org.tigase.messenger.phone.pro.db.DatabaseContract; import org.tigase.messenger.phone.pro.db.DatabaseHelper; import org.tigase.messenger.phone.pro.db.RosterProviderExt; import org.tigase.messenger.phone.pro.notifications.MessageNotification; import org.tigase.messenger.phone.pro.providers.ChatProvider; import org.tigase.messenger.phone.pro.providers.RosterProvider; import org.tigase.messenger.phone.pro.roster.request.SubscriptionRequestActivity; import tigase.jaxmpp.android.Jaxmpp; import tigase.jaxmpp.core.client.*; import tigase.jaxmpp.core.client.exceptions.JaxmppException; import tigase.jaxmpp.core.client.xml.Element; import tigase.jaxmpp.core.client.xml.XMLException; import tigase.jaxmpp.core.client.xmpp.modules.EntityTimeModule; import tigase.jaxmpp.core.client.xmpp.modules.PingModule; import tigase.jaxmpp.core.client.xmpp.modules.SoftwareVersionModule; import tigase.jaxmpp.core.client.xmpp.modules.auth.AuthModule; import tigase.jaxmpp.core.client.xmpp.modules.auth.SaslModule; import tigase.jaxmpp.core.client.xmpp.modules.capabilities.CapabilitiesModule; import tigase.jaxmpp.core.client.xmpp.modules.chat.Chat; import tigase.jaxmpp.core.client.xmpp.modules.chat.MessageCarbonsModule; import tigase.jaxmpp.core.client.xmpp.modules.chat.MessageModule; import tigase.jaxmpp.core.client.xmpp.modules.chat.xep0085.ChatState; import tigase.jaxmpp.core.client.xmpp.modules.chat.xep0085.ChatStateExtension; import tigase.jaxmpp.core.client.xmpp.modules.disco.DiscoveryModule; import tigase.jaxmpp.core.client.xmpp.modules.muc.MucModule; import tigase.jaxmpp.core.client.xmpp.modules.muc.Room; import tigase.jaxmpp.core.client.xmpp.modules.presence.PresenceModule; import tigase.jaxmpp.core.client.xmpp.modules.presence.PresenceStore; import tigase.jaxmpp.core.client.xmpp.modules.roster.RosterModule; import tigase.jaxmpp.core.client.xmpp.modules.streammng.StreamManagementModule; import tigase.jaxmpp.core.client.xmpp.modules.vcard.VCard; import tigase.jaxmpp.core.client.xmpp.modules.vcard.VCardModule; import tigase.jaxmpp.core.client.xmpp.stanzas.*; import tigase.jaxmpp.core.client.xmpp.utils.delay.XmppDelay; import tigase.jaxmpp.j2se.J2SEPresenceStore; import tigase.jaxmpp.j2se.J2SESessionObject; import tigase.jaxmpp.j2se.connectors.socket.SocketConnector; import javax.net.ssl.SSLSocketFactory; import java.util.*; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; public class XMPPService extends Service { public static final String CLIENT_PRESENCE_CHANGED_ACTION = "org.tigase.messenger.phone.pro.PRESENCE_CHANGED"; private static final String KEEPALIVE_ACTION = "org.tigase.messenger.phone.pro.JaxmppService.KEEP_ALIVE"; private final static String TAG = "XMPPService"; private static final StanzaExecutor executor = new StanzaExecutor(); protected final Timer timer = new Timer(); final ScreenStateReceiver screenStateReceiver = new ScreenStateReceiver(); private final AutopresenceManager autopresenceManager = new AutopresenceManager(this); private final IBinder mBinder = new LocalBinder(); private final MultiJaxmpp multiJaxmpp = new MultiJaxmpp(); private final DiscoveryModule.ServerFeaturesReceivedHandler streamHandler = new DiscoveryModule.ServerFeaturesReceivedHandler() { @Override public void onServerFeaturesReceived(final SessionObject sessionObject, IQ stanza, String[] featuresArr) { Set<String> features = new HashSet<String>(Arrays.asList(featuresArr)); if (features.contains(MessageCarbonsModule.XMLNS_MC)) { MessageCarbonsModule mc = multiJaxmpp.get(sessionObject).getModule(MessageCarbonsModule.class); // if we decide to disable MessageCarbons for some account we // may not create module // instance at all, so better be prepared for null here if (mc != null) { try { mc.enable(new AsyncCallback() { @Override public void onError(Stanza responseStanza, XMPPException.ErrorCondition error) throws JaxmppException { Log.v(TAG, "MessageCarbons for account " + sessionObject.getUserBareJid().toString() + " activation failed = " + error.toString()); } @Override public void onSuccess(Stanza responseStanza) throws JaxmppException { Log.v(TAG, "MessageCarbons for account " + sessionObject.getUserBareJid().toString() + " activation succeeded"); } @Override public void onTimeout() throws JaxmppException { Log.v(TAG, "MessageCarbons for account " + sessionObject.getUserBareJid().toString() + " activation timeout"); } }); } catch (JaxmppException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }; private AccountModifyReceiver accountModifyReceiver = new AccountModifyReceiver(); private CapabilitiesDBCache capsCache; private org.tigase.messenger.jaxmpp.android.chat.ChatProvider chatProvider; private ConnectivityManager connManager; private DataRemover dataRemover; private DatabaseHelper dbHelper; private Integer focusedOnChatId = null; private Integer focusedOnRoomId = null; private final Application.ActivityLifecycleCallbacks mActivityCallbacks = new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { Log.i("ActivityLifecycle", "onActivityCreated " + activity); } @Override public void onActivityDestroyed(Activity activity) { Log.i("ActivityLifecycle", "onActivityDestroyed " + activity); } @Override public void onActivityPaused(Activity activity) { Log.i("ActivityLifecycle", "onActivityPaused " + activity); if (activity instanceof ChatActivity) { XMPPService.this.focusedOnChatId = null; } if (activity instanceof MucActivity) { XMPPService.this.focusedOnRoomId = null; } autopresenceManager.start(); } @Override public void onActivityResumed(Activity activity) { Log.i("ActivityLifecycle", "onActivityResumed " + activity); if (activity instanceof ChatActivity) { int v = ((ChatActivity) activity).getOpenChatId(); XMPPService.this.focusedOnChatId = v; Log.i("ActivityLifecycle", "focusedOnChatId = " + v + "; " + ((ChatActivity) activity).getAccount()); } if (activity instanceof MucActivity) { int v = ((MucActivity) activity).getOpenChatId(); XMPPService.this.focusedOnRoomId = v; Log.i("ActivityLifecycle", "focusedOnRoomId = " + v + "; " + ((MucActivity) activity).getAccount()); } autopresenceManager.stop(); } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { Log.i("ActivityLifecycle", "onActivitySaveInstanceState " + activity); } @Override public void onActivityStarted(Activity activity) { Log.i("ActivityLifecycle", "onActivityStarted " + activity); } @Override public void onActivityStopped(Activity activity) { Log.i("ActivityLifecycle", "onActivityStopped " + activity); // sendAcks(); } }; private long keepaliveInterval = 1000 * 60 * 3; private HashSet<SessionObject> locked = new HashSet<SessionObject>(); private MessageHandler messageHandler; private MobileModeFeature mobileModeFeature; private JaxmppCore.LoggedInHandler jaxmppConnectedHandler = new JaxmppCore.LoggedInHandler() { @Override public void onLoggedIn(SessionObject sessionObject) { Log.i("XMPPService", "JAXMPP connected " + sessionObject.getUserBareJid()); final Jaxmpp jaxmpp = multiJaxmpp.get(sessionObject); try { mobileModeFeature.accountConnected(jaxmpp); } catch (JaxmppException e) { Log.e(TAG, "Exception processing MobileModeFeature on connect for account " + sessionObject.getUserBareJid().toString()); } (new SendUnsentMessages(sessionObject)).execute(); (new RejoinToMucRooms(sessionObject)).execute(); } }; private MucHandler mucHandler; private MessageNotification notificationHelper = new MessageNotification(); private OwnPresenceFactoryImpl ownPresenceStanzaFactory; private PresenceHandler presenceHandler; private RosterProviderExt rosterProvider; private final PresenceModule.SubscribeRequestHandler subscribeHandler = new PresenceModule.SubscribeRequestHandler() { @Override public void onSubscribeRequest(SessionObject sessionObject, Presence stanza, BareJID jid) { XMPPService.this.processSubscriptionRequest(sessionObject, stanza, jid); } }; private SSLSocketFactory sslSocketFactory; private int usedNetworkType; final BroadcastReceiver presenceChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { long presenceId = intent.getLongExtra("presence", CPresence.ONLINE); if (presenceId == CPresence.OFFLINE) { disconnectAllJaxmpp(true); } else { processPresenceUpdate(); } } }; private final BroadcastReceiver connReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { NetworkInfo netInfo = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)) .getActiveNetworkInfo(); onNetworkChange(netInfo); } }; private JaxmppCore.LoggedOutHandler jaxmppDisconnectedHandler = new JaxmppCore.LoggedOutHandler() { @Override public void onLoggedOut(SessionObject sessionObject) { Jaxmpp jaxmpp = multiJaxmpp.get(sessionObject); Log.i("XMPPService", "JAXMPP disconnected " + sessionObject.getUserBareJid()); if (getUsedNetworkType() != -1) { if (jaxmpp != null) { XMPPService.this.connectJaxmpp(jaxmpp, 5 * 1000L); } } } }; public XMPPService() { Logger logger = Logger.getLogger("tigase.jaxmpp"); Handler handler = new AndroidLoggingHandler(); handler.setLevel(Level.ALL); logger.addHandler(handler); logger.setLevel(Level.ALL); } private void connectAllJaxmpp(Long delay) { setUsedNetworkType(getActiveNetworkType()); // geolocationFeature.registerLocationListener(); for (final JaxmppCore jaxmpp : multiJaxmpp.get()) { Log.v(TAG, "connecting account " + jaxmpp.getSessionObject().getUserBareJid()); connectJaxmpp((Jaxmpp) jaxmpp, delay); } } private void connectAllJaxmpp() { for (final JaxmppCore jaxmpp : multiJaxmpp.get()) { connectJaxmpp((Jaxmpp) jaxmpp, (Long) null); } } private void connectJaxmpp(final Jaxmpp jaxmpp, final Date date) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); long presenceId = sharedPref.getLong("presence", CPresence.OFFLINE); if (presenceId == CPresence.OFFLINE) return; if (isLocked(jaxmpp.getSessionObject())) { Log.v(TAG, "cancelling connect for " + jaxmpp.getSessionObject().getUserBareJid() + " because it is locked"); return; } final Runnable r = new Runnable() { @Override public void run() { Log.v(TAG, "Trying to connect account " + jaxmpp.getSessionObject().getUserBareJid()); lock(jaxmpp.getSessionObject(), false); if (isDisabled(jaxmpp.getSessionObject())) { Log.v(TAG, "cancelling connect for " + jaxmpp.getSessionObject().getUserBareJid() + " because it is disabled"); return; } setUsedNetworkType(getActiveNetworkType()); int tmpNetworkType = getUsedNetworkType(); Log.v(TAG, "Network state is " + tmpNetworkType); if (tmpNetworkType != -1) { (new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { if (jaxmpp.isConnected()) { Log.v(TAG, "cancelling connect for " + jaxmpp.getSessionObject().getUserBareJid() + " because it is connected already"); return null; } final Connector.State state = jaxmpp.getSessionObject() .getProperty(Connector.CONNECTOR_STAGE_KEY); Log.v(TAG, "Account " + jaxmpp.getSessionObject().getUserBareJid() + " is in state " + state); if (state != null && state != Connector.State.disconnected) { Log.v(TAG, "cancelling connect for " + jaxmpp.getSessionObject().getUserBareJid() + " because it state " + state); return null; } jaxmpp.getSessionObject().setProperty("messenger#error", null); jaxmpp.login(); } catch (Exception e) { if (e.getCause() instanceof SecureTrustManagerFactory.DataCertificateException) { jaxmpp.getSessionObject().setProperty("CC:DISABLED", Boolean.TRUE); processCertificateError(jaxmpp, (SecureTrustManagerFactory.DataCertificateException) e.getCause()); } else { Log.e(TAG, "Can't connect account " + jaxmpp.getSessionObject().getUserBareJid(), e); } } return null; } }).execute(); } } }; lock(jaxmpp.getSessionObject(), true); if (date == null) { Log.d(TAG, "Starting connection NOW"); r.run(); } else { Log.d(TAG, "Starting connection LATER"); timer.schedule(new TimerTask() { @Override public void run() { r.run(); } }, date); } } private void connectJaxmpp(final Jaxmpp jaxmpp, final Long delay) { connectJaxmpp(jaxmpp, delay == null ? null : new Date(delay + System.currentTimeMillis())); } private Jaxmpp createJaxmpp(final BareJID accountJid, final int accountId) { final SessionObject sessionObject = new J2SESessionObject(); sessionObject.setUserProperty(SessionObject.USER_BARE_JID, accountJid); try { PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); String versionName = pinfo.versionName; sessionObject.setUserProperty(SoftwareVersionModule.VERSION_KEY, versionName); } catch (Exception e) { } PresenceModule.setOwnPresenceStanzaFactory(sessionObject, this.ownPresenceStanzaFactory); sessionObject.setUserProperty(Connector.TRUST_MANAGERS_KEY, SecureTrustManagerFactory.getTrustManagers(getBaseContext())); sessionObject.setUserProperty(SoftwareVersionModule.NAME_KEY, getString(R.string.about_application_name)); sessionObject.setUserProperty(SoftwareVersionModule.OS_KEY, "Android " + android.os.Build.VERSION.RELEASE); sessionObject.setUserProperty(DiscoveryModule.IDENTITY_CATEGORY_KEY, "client"); sessionObject.setUserProperty(DiscoveryModule.IDENTITY_TYPE_KEY, "phone"); sessionObject.setUserProperty(CapabilitiesModule.NODE_NAME_KEY, "http://tigase.org/messenger"); sessionObject.setUserProperty("ID", (long) accountId); sessionObject.setUserProperty(SocketConnector.SERVER_PORT, 5222); sessionObject.setUserProperty(tigase.jaxmpp.j2se.Jaxmpp.CONNECTOR_TYPE, "socket"); sessionObject.setUserProperty(Connector.EXTERNAL_KEEPALIVE_KEY, true); sessionObject.setUserProperty(SocketConnector.SERVER_PORT, 5222); sessionObject.setUserProperty(tigase.jaxmpp.j2se.Jaxmpp.CONNECTOR_TYPE, "socket"); sessionObject.setUserProperty(Connector.EXTERNAL_KEEPALIVE_KEY, true); sessionObject.setUserProperty(JaxmppCore.AUTOADD_STANZA_ID_KEY, Boolean.TRUE); // sessionObject.setUserProperty(SocketConnector.SSL_SOCKET_FACTORY_KEY, // sslSocketFactory); final Jaxmpp jaxmpp = new Jaxmpp(sessionObject); jaxmpp.setExecutor(executor); RosterModule.setRosterStore(sessionObject, new AndroidRosterStore(this.rosterProvider)); jaxmpp.getModulesManager().register(new RosterModule(this.rosterProvider)); PresenceModule.setPresenceStore(sessionObject, new J2SEPresenceStore()); jaxmpp.getModulesManager().register(new PresenceModule()); jaxmpp.getModulesManager().register(new VCardModule()); AndroidChatManager chatManager = new AndroidChatManager(this.chatProvider); MessageModule messageModule = new MessageModule(chatManager); jaxmpp.getModulesManager().register(messageModule); messageModule.addExtension(new ChatStateExtension(chatManager)); jaxmpp.getModulesManager().register(new MucModule(new AndroidRoomsManager(this.chatProvider))); jaxmpp.getModulesManager().register(new PingModule()); jaxmpp.getModulesManager().register(new EntityTimeModule()); CapabilitiesModule capsModule = new CapabilitiesModule(); capsModule.setCache(capsCache); jaxmpp.getModulesManager().register(capsModule); try { jaxmpp.getModulesManager().register(new MessageCarbonsModule()); } catch (JaxmppException ex) { Log.v(TAG, "Exception creating instance of MessageCarbonsModule", ex); } return jaxmpp; } private void disconnectAllJaxmpp(final boolean cleaning) { setUsedNetworkType(-1); // if (geolocationFeature != null) { // geolocationFeature.unregisterLocationListener(); // } for (final JaxmppCore j : multiJaxmpp.get()) { disconnectJaxmpp((Jaxmpp) j, cleaning); } // synchronized (connectionErrorsCounter) { // connectionErrorsCounter.clear(); // } } private void disconnectJaxmpp(final Jaxmpp jaxmpp, final boolean cleaning) { (new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { // geolocationFeature.accountDisconnect(jaxmpp); if (jaxmpp.isConnected()) jaxmpp.disconnect(false); // is this needed any more?? if (cleaning || !StreamManagementModule.isResumptionEnabled(jaxmpp.getSessionObject())) { XMPPService.this.rosterProvider.resetStatus(jaxmpp.getSessionObject()); } } catch (Exception e) { Log.e(TAG, "cant; disconnect account " + jaxmpp.getSessionObject().getUserBareJid(), e); } return null; } }).execute(); } private int getActiveNetworkType() { NetworkInfo info = connManager.getActiveNetworkInfo(); if (info == null) return -1; if (!info.isConnected()) return -1; return info.getType(); } public Jaxmpp getJaxmpp(String account) { return this.multiJaxmpp.get(BareJID.bareJIDInstance(account)); } public Jaxmpp getJaxmpp(BareJID account) { return this.multiJaxmpp.get(account); } public MultiJaxmpp getMultiJaxmpp() { return this.multiJaxmpp; } protected final Connector.State getState(SessionObject object) { Connector.State state = multiJaxmpp.get(object).getSessionObject() .getProperty(Connector.CONNECTOR_STAGE_KEY); return state == null ? Connector.State.disconnected : state; } private int getUsedNetworkType() { return this.usedNetworkType; } private void setUsedNetworkType(int type) { this.usedNetworkType = type; } public boolean isDisabled(SessionObject sessionObject) { Boolean x = sessionObject.getProperty("CC:DISABLED"); return x == null ? false : x; } private boolean isLocked(SessionObject sessionObject) { synchronized (locked) { return locked.contains(sessionObject); } } private void keepAlive() { (new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { for (JaxmppCore jaxmpp : multiJaxmpp.get()) { try { if (jaxmpp.isConnected()) { Log.i("XMPPService", "Sending keepAlive for " + jaxmpp.getSessionObject().getUserBareJid()); jaxmpp.getConnector().keepalive(); // GeolocationFeature.sendQueuedGeolocation(jaxmpp, // JaxmppService.this); } else if (Connector.State.disconnecting == jaxmpp.getSessionObject() .getProperty(Connector.CONNECTOR_STAGE_KEY)) { // if jaxmpp hangs on 'disconnecting' state for more // than 45 seconds, stop Connector. final Date x = jaxmpp.getSessionObject() .getProperty(Connector.CONNECTOR_STAGE_TIMESTAMP_KEY); if (x != null && x.getTime() < System.currentTimeMillis() - 45 * 1000) { jaxmpp.getConnector().stop(true); } } } catch (JaxmppException ex) { Log.e(TAG, "error sending keep alive for = " + jaxmpp.getSessionObject().getUserBareJid().toString(), ex); } } return null; } }).execute(); } private void lock(SessionObject sessionObject, boolean value) { synchronized (locked) { if (value) { locked.add(sessionObject); } else { locked.remove(sessionObject); } } } public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { super.onCreate(); Log.i("XMPPService", "Service started"); getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks); this.ownPresenceStanzaFactory = new OwnPresenceFactoryImpl(); this.dbHelper = DatabaseHelper.getInstance(this); this.connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); this.dataRemover = new DataRemover(this.dbHelper); SSLSessionCache sslSessionCache = new SSLSessionCache(this); this.sslSocketFactory = SSLCertificateSocketFactory.getDefault(0, sslSessionCache); this.rosterProvider = new RosterProviderExt(this, dbHelper, new RosterProviderExt.Listener() { @Override public void onChange(Long rosterItemId) { Uri uri = rosterItemId != null ? ContentUris.withAppendedId(RosterProvider.ROSTER_URI, rosterItemId) : RosterProvider.ROSTER_URI; Log.i(TAG, "Content change: " + uri); getApplicationContext().getContentResolver().notifyChange(uri, null); } }, "roster_version"); rosterProvider.resetStatus(); this.mobileModeFeature = new MobileModeFeature(this); this.presenceHandler = new PresenceHandler(this); this.messageHandler = new MessageHandler(this); this.chatProvider = new org.tigase.messenger.jaxmpp.android.chat.ChatProvider(this, dbHelper, new org.tigase.messenger.jaxmpp.android.chat.ChatProvider.Listener() { @Override public void onChange(Long chatId) { Uri uri = chatId != null ? ContentUris.withAppendedId(ChatProvider.OPEN_CHATS_URI, chatId) : ChatProvider.OPEN_CHATS_URI; getApplicationContext().getContentResolver().notifyChange(uri, null); } }); chatProvider.resetRoomState(CPresence.OFFLINE); this.mucHandler = new MucHandler(); this.capsCache = new CapabilitiesDBCache(dbHelper); IntentFilter screenStateReceiverFilter = new IntentFilter(Intent.ACTION_SCREEN_ON); screenStateReceiverFilter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(screenStateReceiver, screenStateReceiverFilter); registerReceiver(presenceChangedReceiver, new IntentFilter(CLIENT_PRESENCE_CHANGED_ACTION)); registerReceiver(connReceiver, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")); multiJaxmpp.addHandler( StreamManagementModule.StreamManagementFailedHandler.StreamManagementFailedEvent.class, new StreamManagementModule.StreamManagementFailedHandler() { @Override public void onStreamManagementFailed(final SessionObject sessionObject, XMPPException.ErrorCondition condition) { if (condition != null && condition.getElementName().equals("item-not-found")) { XMPPService.this.rosterProvider.resetStatus(sessionObject); } } }); multiJaxmpp.addHandler(DiscoveryModule.ServerFeaturesReceivedHandler.ServerFeaturesReceivedEvent.class, streamHandler); multiJaxmpp.addHandler(JaxmppCore.LoggedInHandler.LoggedInEvent.class, jaxmppConnectedHandler); multiJaxmpp.addHandler(JaxmppCore.LoggedOutHandler.LoggedOutEvent.class, jaxmppDisconnectedHandler); // multiJaxmpp.addHandler(SocketConnector.ErrorHandler.ErrorEvent.class, // ); // // this.connectorListener = new Connector.ErrorHandler() { // // @Override // public void onError(SessionObject sessionObject, StreamError // condition, Throwable caught) throws JaxmppException { // AbstractSocketXmppSessionLogic.this.processConnectorErrors(condition, // caught); // } // }; multiJaxmpp.addHandler(PresenceModule.ContactAvailableHandler.ContactAvailableEvent.class, presenceHandler); multiJaxmpp.addHandler(PresenceModule.ContactUnavailableHandler.ContactUnavailableEvent.class, presenceHandler); multiJaxmpp.addHandler(PresenceModule.ContactChangedPresenceHandler.ContactChangedPresenceEvent.class, presenceHandler); multiJaxmpp.addHandler(PresenceModule.SubscribeRequestHandler.SubscribeRequestEvent.class, subscribeHandler); multiJaxmpp.addHandler(MessageModule.MessageReceivedHandler.MessageReceivedEvent.class, messageHandler); multiJaxmpp.addHandler(MessageCarbonsModule.CarbonReceivedHandler.CarbonReceivedEvent.class, messageHandler); multiJaxmpp.addHandler(ChatStateExtension.ChatStateChangedHandler.ChatStateChangedEvent.class, messageHandler); multiJaxmpp.addHandler(AuthModule.AuthFailedHandler.AuthFailedEvent.class, new AuthModule.AuthFailedHandler() { @Override public void onAuthFailed(SessionObject sessionObject, SaslModule.SaslError error) throws JaxmppException { processAuthenticationError((Jaxmpp) multiJaxmpp.get(sessionObject)); } }); multiJaxmpp.addHandler(MucModule.MucMessageReceivedHandler.MucMessageReceivedEvent.class, mucHandler); multiJaxmpp.addHandler(MucModule.MessageErrorHandler.MessageErrorEvent.class, mucHandler); multiJaxmpp.addHandler(MucModule.YouJoinedHandler.YouJoinedEvent.class, mucHandler); multiJaxmpp.addHandler(MucModule.StateChangeHandler.StateChangeEvent.class, mucHandler); multiJaxmpp.addHandler(MucModule.PresenceErrorHandler.PresenceErrorEvent.class, mucHandler); IntentFilter filterAccountModifyReceiver = new IntentFilter(); filterAccountModifyReceiver.addAction(LoginActivity.ACCOUNT_MODIFIED_MSG); filterAccountModifyReceiver.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); registerReceiver(accountModifyReceiver, filterAccountModifyReceiver); startKeepAlive(); updateJaxmppInstances(); // connectAllJaxmpp(); } @Override public void onDestroy() { Log.i("XMPPService", "Service destroyed"); unregisterReceiver(screenStateReceiver); unregisterReceiver(connReceiver); unregisterReceiver(presenceChangedReceiver); unregisterReceiver(accountModifyReceiver); getApplication().unregisterActivityLifecycleCallbacks(mActivityCallbacks); disconnectAllJaxmpp(true); super.onDestroy(); mobileModeFeature = null; sendBroadcast(new Intent(ServiceRestarter.ACTION_NAME)); } private void onNetworkChange(final NetworkInfo netInfo) { if (netInfo != null && netInfo.isConnected()) { connectAllJaxmpp(5000l); } else { disconnectAllJaxmpp(false); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && "connect-all".equals(intent.getAction())) { connectAllJaxmpp(); } else if (intent != null && KEEPALIVE_ACTION.equals(intent.getAction())) { keepAlive(); } return super.onStartCommand(intent, flags, startId); } private void processAuthenticationError(final Jaxmpp jaxmpp) { Log.e(TAG, "Invalid credentials of account " + jaxmpp.getSessionObject().getUserBareJid()); jaxmpp.getSessionObject().setUserProperty("CC:DISABLED", true); String title = getString(R.string.notification_credentials_error_title, jaxmpp.getSessionObject().getUserBareJid().toString()); String text = getString(R.string.notification_certificate_error_text); Intent resultIntent = new Intent(this, LoginActivity.class); resultIntent.putExtra("account_name", jaxmpp.getSessionObject().getUserBareJid().toString()); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); // Adds the back stack for the Intent (but not the Intent itself) stackBuilder.addParentStack(ChatActivity.class); // Adds the Intent that starts the Activity to the top of the stack stackBuilder.addNextIntent(resultIntent); PendingIntent editServerSettingsPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this) // .setSmallIcon(R.drawable.ic_messenger_icon) .setSmallIcon(android.R.drawable.stat_notify_error).setWhen(System.currentTimeMillis()) .setAutoCancel(true).setTicker(title).setContentTitle(title).setContentText(text) .setContentIntent(editServerSettingsPendingIntent) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); builder.setLights(0xffff0000, 100, 100); // getNotificationManager().notify(notificationId, builder.build()); NotificationManager mNotificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE); mNotificationManager.notify(("error:" + jaxmpp.getSessionObject().getUserBareJid().toString()).hashCode(), builder.build()); } private void processCertificateError(final Jaxmpp jaxmpp, final SecureTrustManagerFactory.DataCertificateException cause) { Log.e(TAG, "Invalid certificate of account " + jaxmpp.getSessionObject().getUserBareJid() + ": " + cause.getMessage()); jaxmpp.getSessionObject().setUserProperty("CC:DISABLED", true); String title = getString(R.string.notification_certificate_error_title, jaxmpp.getSessionObject().getUserBareJid().toString()); String text = getString(R.string.notification_certificate_error_text); Intent resultIntent = new Intent(this, LoginActivity.class); resultIntent.putExtra("account_name", jaxmpp.getSessionObject().getUserBareJid().toString()); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); // Adds the back stack for the Intent (but not the Intent itself) stackBuilder.addParentStack(ChatActivity.class); // Adds the Intent that starts the Activity to the top of the stack stackBuilder.addNextIntent(resultIntent); PendingIntent editServerSettingsPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this) // .setSmallIcon(R.drawable.ic_messenger_icon) .setSmallIcon(android.R.drawable.stat_notify_error).setWhen(System.currentTimeMillis()) .setAutoCancel(true).setTicker(title).setContentTitle(title).setContentText(text) .setContentIntent(editServerSettingsPendingIntent) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); builder.setLights(0xffff0000, 100, 100); // getNotificationManager().notify(notificationId, builder.build()); NotificationManager mNotificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE); mNotificationManager.notify(("error:" + jaxmpp.getSessionObject().getUserBareJid().toString()).hashCode(), builder.build()); } void processPresenceUpdate() { (new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { for (JaxmppCore jaxmpp : multiJaxmpp.get()) { try { if (!jaxmpp.isConnected()) { connectJaxmpp((Jaxmpp) jaxmpp, (Long) null); } else { jaxmpp.getModule(PresenceModule.class).sendInitialPresence(); } } catch (JaxmppException e) { Log.e("TAG", "Can't update presence", e); } } return null; } }).execute(); } private void processSubscriptionRequest(final SessionObject sessionObject, final Presence stanza, final BareJID jid) { Log.e(TAG, "Subscription request from " + jid); retrieveVCard(sessionObject, jid); String title = getString(R.string.notification_subscription_request_title, jid.toString()); String text = getString(R.string.notification_subscription_request_text); Intent resultIntent = new Intent(this, SubscriptionRequestActivity.class); resultIntent.putExtra("account_name", sessionObject.getUserBareJid().toString()); resultIntent.putExtra("jid", jid.toString()); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); // Adds the back stack for the Intent (but not the Intent itself) stackBuilder.addParentStack(ChatActivity.class); // Adds the Intent that starts the Activity to the top of the stack stackBuilder.addNextIntent(resultIntent); PendingIntent editServerSettingsPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this) // .setSmallIcon(R.drawable.ic_messenger_icon) .setSmallIcon(R.drawable.ic_messenger_icon).setWhen(System.currentTimeMillis()).setAutoCancel(true) .setTicker(title).setContentTitle(title).setContentText(text) .setContentIntent(editServerSettingsPendingIntent) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); builder.setLights(0xff0000ff, 100, 100); // getNotificationManager().notify(notificationId, builder.build()); NotificationManager mNotificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE); mNotificationManager.notify(("request:" + sessionObject.getUserBareJid().toString() + ":" + jid).hashCode(), builder.build()); } private void retrieveVCard(final SessionObject sessionObject, final BareJID jid) { try { JaxmppCore jaxmpp = multiJaxmpp.get(sessionObject); if (jaxmpp == null || !jaxmpp.isConnected()) return; // final RosterItem rosterItem = jaxmpp.getRoster().get(jid); VCardModule vcardModule = jaxmpp.getModule(VCardModule.class); if (vcardModule != null) { vcardModule.retrieveVCard(JID.jidInstance(jid), (long) 3 * 60 * 1000, new VCardModule.VCardAsyncCallback() { @Override public void onError(Stanza responseStanza, XMPPException.ErrorCondition error) throws JaxmppException { } @Override public void onTimeout() throws JaxmppException { } @Override protected void onVCardReceived(VCard vcard) throws XMLException { try { if (vcard.getPhotoVal() != null && vcard.getPhotoVal().length() > 0) { byte[] buffer = Base64.decode(vcard.getPhotoVal()); rosterProvider.updateVCardHash(sessionObject, jid, buffer); Intent intent = new Intent("org.tigase.messenger.phone.pro.AvatarUpdated"); intent.putExtra("jid", jid.toString()); XMPPService.this.sendBroadcast(intent); } } catch (Exception e) { Log.e("tigase", "WTF?", e); } } }); } } catch (Exception e) { Log.e("tigase", "WTF?", e); } } private void sendAcks() { (new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { for (JaxmppCore jaxmpp : multiJaxmpp.get()) { try { if (jaxmpp.isConnected()) { Log.d("XMPPService", "Sending ACK for " + jaxmpp.getSessionObject().getUserBareJid()); jaxmpp.getModule(StreamManagementModule.class).sendAck(); } } catch (JaxmppException ex) { Log.e(TAG, "error sending ACK for = " + jaxmpp.getSessionObject().getUserBareJid().toString(), ex); } } return null; } }).execute(); } private void sendNotification(SessionObject sessionObject, Chat chat, Message msg) throws JaxmppException { Log.i("ActivityLifecycle", "focused=" + focusedOnChatId + "; chatId=" + chat.getId()); if (this.focusedOnChatId != null && chat.getId() == this.focusedOnChatId) return; SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); if (!sharedPref.getBoolean("notifications_new_message", true)) return; notificationHelper.show(this, chat, msg); } private void sendNotification(SessionObject sessionObject, Room room, Message msg) throws JaxmppException { Log.i("ActivityLifecycle", "focused=" + focusedOnRoomId + "; chatId=" + room.getId()); if (this.focusedOnRoomId != null && room.getId() == this.focusedOnRoomId) return; String body = msg.getBody(); boolean mentioned = body != null && body.toLowerCase().contains(room.getNickname().toLowerCase()); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); if (!mentioned && !sharedPref.getBoolean("notifications_new_groupmessage", true)) return; notificationHelper.show(this, room, msg); } private void startKeepAlive() { Intent i = new Intent(); i.setClass(this, XMPPService.class); i.setAction(KEEPALIVE_ACTION); PendingIntent pi = PendingIntent.getService(this, 0, i, 0); AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE); alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + keepaliveInterval, keepaliveInterval, pi); } private void stopKeepAlive() { Intent i = new Intent(); i.setClass(this, XMPPService.class); i.setAction(KEEPALIVE_ACTION); PendingIntent pi = PendingIntent.getService(this, 0, i, 0); AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE); alarmMgr.cancel(pi); } private boolean storeMessage(SessionObject sessionObject, Chat chat, tigase.jaxmpp.core.client.xmpp.stanzas.Message msg) throws XMLException { // for now let's ignore messages without body element if (msg.getBody() == null && msg.getType() != StanzaType.error) return false; BareJID authorJid = msg.getFrom() == null ? sessionObject.getUserBareJid() : msg.getFrom().getBareJid(); String author = authorJid.toString(); String jid = null; if (chat != null) { jid = chat.getJid().getBareJid().toString(); } else { jid = (sessionObject.getUserBareJid().equals(authorJid) ? msg.getTo().getBareJid() : authorJid) .toString(); } ContentValues values = new ContentValues(); values.put(DatabaseContract.ChatHistory.FIELD_AUTHOR_JID, author); values.put(DatabaseContract.ChatHistory.FIELD_JID, jid); XmppDelay delay = XmppDelay.extract(msg); values.put(DatabaseContract.ChatHistory.FIELD_TIMESTAMP, ((delay == null || delay.getStamp() == null) ? new Date() : delay.getStamp()).getTime()); if (msg.getType() == StanzaType.error) { if (chat == null) { Log.e(TAG, "Error message from " + jid + " has no Chat. Skipping store."); return false; } ErrorElement error = ErrorElement.extract(msg); String body = "Error: "; if (error != null) { if (error.getText() != null) { body += error.getText(); } else { XMPPException.ErrorCondition errorCondition = error.getCondition(); if (errorCondition != null) { body += errorCondition.getElementName(); } } } if (msg.getBody() != null) { body += "\n ------ \n"; body += msg.getBody(); } values.put(DatabaseContract.ChatHistory.FIELD_BODY, body); values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_ERROR); } else { values.put(DatabaseContract.ChatHistory.FIELD_BODY, msg.getBody()); Element geoloc = msg.getChildrenNS("geoloc", "http://jabber.org/protocol/geoloc"); if (geoloc != null) { values.put(DatabaseContract.ChatHistory.FIELD_DATA, geoloc.getAsString()); values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_LOCALITY); } else { values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_MESSAGE); } } values.put(DatabaseContract.ChatHistory.FIELD_STANZA_ID, msg.getId()); if (chat != null) { values.put(DatabaseContract.ChatHistory.FIELD_THREAD_ID, chat.getThreadId()); } values.put(DatabaseContract.ChatHistory.FIELD_ACCOUNT, sessionObject.getUserBareJid().toString()); // DatabaseContract.ChatHistory.ITEM_TYPE_MESSAGE; if (sessionObject.getUserBareJid().equals(authorJid)) { values.put(DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.STATE_OUT_SENT); } else { values.put(DatabaseContract.ChatHistory.FIELD_STATE, focusedOnChatId != null && chat.getId() == focusedOnChatId ? DatabaseContract.ChatHistory.STATE_INCOMING : DatabaseContract.ChatHistory.STATE_INCOMING_UNREAD); } Uri uri = Uri.parse(ChatProvider.CHAT_HISTORY_URI + "/" + sessionObject.getUserBareJid() + "/" + jid); uri = getContentResolver().insert(uri, values); getApplicationContext().getContentResolver() .notifyChange(ContentUris.withAppendedId(ChatProvider.OPEN_CHATS_URI, chat.getId()), null); // getApplicationContext().getContentResolver().notifyChange(uri, null); // context.getContentResolver().insert(uri, values); // if (!sessionObject.getUserBareJid().equals(authorJid) && // showNotification // && (this.activeChatJid == null || // !this.activeChatJid.getBareJid().equals(authorJid))) { // notificationHelper.notifyNewChatMessage(sessionObject, msg); // } return true; } private final void updateJaxmppInstances() { final HashSet<BareJID> accountsJids = new HashSet<BareJID>(); for (JaxmppCore jaxmpp : multiJaxmpp.get()) { accountsJids.add(jaxmpp.getSessionObject().getUserBareJid()); } final AccountManager am = AccountManager.get(this); for (Account account : am.getAccountsByType(Authenticator.ACCOUNT_TYPE)) { BareJID accountJid = BareJID.bareJIDInstance(account.name); Jaxmpp jaxmpp = multiJaxmpp.get(accountJid); if (jaxmpp == null) { jaxmpp = createJaxmpp(accountJid, account.hashCode()); multiJaxmpp.add(jaxmpp); } // workaround for unknown certificate error jaxmpp.getSessionObject().setProperty("jaxmpp#ThrowedException", null); String password = am.getPassword(account); String nickname = am.getUserData(account, AccountsConstants.FIELD_NICKNAME); String hostname = am.getUserData(account, AccountsConstants.FIELD_HOSTNAME); String resource = am.getUserData(account, AccountsConstants.FIELD_RESOURCE); hostname = hostname == null ? null : hostname.trim(); jaxmpp.getSessionObject().setUserProperty(SessionObject.PASSWORD, password); jaxmpp.getSessionObject().setUserProperty(SessionObject.NICKNAME, nickname); if (hostname != null && TextUtils.isEmpty(hostname)) hostname = null; // sessionObject.setUserProperty(SessionObject.DOMAIN_NAME, // hostname); if (TextUtils.isEmpty(resource)) resource = null; jaxmpp.getSessionObject().setUserProperty(SessionObject.RESOURCE, resource); MobileModeFeature.updateSettings(account, jaxmpp, this); boolean disabled = !Boolean.parseBoolean(am.getUserData(account, AccountsConstants.FIELD_ACTIVE)); jaxmpp.getSessionObject().setUserProperty("CC:DISABLED", disabled); if (disabled) { if (jaxmpp.isConnected()) { this.disconnectJaxmpp(jaxmpp, true); } } else { if (!jaxmpp.isConnected()) { this.connectJaxmpp(jaxmpp, 1L); } } accountsJids.remove(accountJid); } for (BareJID accountJid : accountsJids) { final Jaxmpp jaxmpp = multiJaxmpp.get(accountJid); if (jaxmpp != null) { multiJaxmpp.remove(jaxmpp); (new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { jaxmpp.disconnect(); // clear presences for account? // app.clearPresences(jaxmpp.getSessionObject(), // false); // is this needed any more?? // JaxmppService.this.rosterProvider.resetStatus(jaxmpp.getSessionObject()); } catch (Exception ex) { Log.e(TAG, "Can't disconnect", ex); } return null; } }).execute(); } } dataRemover.removeUnusedData(this); } protected synchronized void updateRosterItem(final SessionObject sessionObject, final Presence p) throws XMLException { if (p != null) { Element x = p.getChildrenNS("x", "vcard-temp:x:update"); if (x != null) { for (Element c : x.getChildren()) { if (c.getName().equals("photo") && c.getValue() != null) { boolean retrieve = false; final String sha = c.getValue(); if (sha == null) continue; retrieve = !rosterProvider.checkVCardHash(sessionObject, p.getFrom().getBareJid(), sha); if (retrieve) retrieveVCard(sessionObject, p.getFrom().getBareJid()); } } } } // Synchronize contact status BareJID from = p.getFrom().getBareJid(); PresenceStore store = PresenceModule.getPresenceStore(sessionObject); Presence bestPresence = store.getBestPresence(from); // SyncAdapter.syncContactStatus(getApplicationContext(), // sessionObject.getUserBareJid(), from, bestPresence); } private class AccountModifyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.i("XMPPService", "Updating accounts !" + intent.getAction()); updateJaxmppInstances(); for (JaxmppCore j : multiJaxmpp.get()) { Connector.State st = getState(j.getSessionObject()); if (st == Connector.State.disconnected || st == null) { connectJaxmpp((Jaxmpp) j, (Long) null); } } } } public class LocalBinder extends Binder { public XMPPService getService() { // Return this instance of LocalService so clients can call public // methods return XMPPService.this; } } private class MessageHandler implements MessageModule.MessageReceivedHandler, MessageCarbonsModule.CarbonReceivedHandler, ChatStateExtension.ChatStateChangedHandler { private final Context context; private final MarkAsRead markAsRead; public MessageHandler(XMPPService xmppService) { this.context = xmppService.getApplicationContext(); this.markAsRead = new MarkAsRead(context); } @Override public void onCarbonReceived(SessionObject sessionObject, MessageCarbonsModule.CarbonEventType carbonType, tigase.jaxmpp.core.client.xmpp.stanzas.Message msg, Chat chat) { try { boolean stored = storeMessage(sessionObject, chat, msg); if (stored && carbonType == MessageCarbonsModule.CarbonEventType.sent) { markAsRead.markChatAsRead(chat.getId(), sessionObject.getUserBareJid(), chat.getJid()); } } catch (Exception ex) { Log.e(TAG, "Exception handling received carbon message", ex); } } @Override public void onChatStateChanged(SessionObject sessionObject, Chat chat, ChatState state) { try { Log.v(TAG, "received chat state chaged event for " + chat.getJid().toString() + ", new state = " + state); Uri uri = chat != null ? ContentUris.withAppendedId(ChatProvider.OPEN_CHATS_URI, chat.getId()) : ChatProvider.OPEN_CHATS_URI; getApplicationContext().getContentResolver().notifyChange(uri, null); } catch (Exception ex) { Log.e(TAG, "Exception handling received chat state change event", ex); } } @Override public void onMessageReceived(SessionObject sessionObject, Chat chat, tigase.jaxmpp.core.client.xmpp.stanzas.Message msg) { try { boolean stored = storeMessage(sessionObject, chat, msg); if (stored && msg.getBody() != null && !msg.getBody().isEmpty()) { sendNotification(sessionObject, chat, msg); } } catch (Exception ex) { Log.e(TAG, "Exception handling received message", ex); } } } private class MucHandler implements MucModule.MucMessageReceivedHandler, MucModule.YouJoinedHandler, MucModule.MessageErrorHandler, MucModule.StateChangeHandler, MucModule.PresenceErrorHandler { @Override public void onMessageError(SessionObject sessionObject, tigase.jaxmpp.core.client.xmpp.stanzas.Message msg, Room room, String nickname, Date timestamp) { try { Log.e(TAG, "Error from room " + room.getRoomJid() + ", error = " + msg.getAsString()); } catch (XMLException e) { } onMucMessageReceived(sessionObject, msg, room, nickname, timestamp); } @Override public void onMucMessageReceived(SessionObject sessionObject, tigase.jaxmpp.core.client.xmpp.stanzas.Message msg, Room room, String nickname, Date timestamp) { try { Log.d(TAG, "Received groupchat message: " + msg.getBody() + "; room=" + room); if (msg == null || msg.getBody() == null || room == null) return; String body = msg.getBody(); ContentValues values = new ContentValues(); values.put(DatabaseContract.ChatHistory.FIELD_JID, room.getRoomJid().toString()); values.put(DatabaseContract.ChatHistory.FIELD_AUTHOR_NICKNAME, nickname); values.put(DatabaseContract.ChatHistory.FIELD_TIMESTAMP, timestamp.getTime()); values.put(DatabaseContract.ChatHistory.FIELD_STANZA_ID, msg.getId()); boolean notify = false; if (msg.getType() == StanzaType.error) { values.put(DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.STATE_INCOMING); values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_ERROR); ErrorElement error = ErrorElement.extract(msg); body = "Error: "; if (error != null) { if (error.getText() != null) { body += error.getText(); } else { XMPPException.ErrorCondition errorCondition = error.getCondition(); if (errorCondition != null) { body += errorCondition.getElementName(); } } } if (msg.getBody() != null) { body += "\n ------ \n"; body += msg.getBody(); } values.put(DatabaseContract.ChatHistory.FIELD_BODY, body); } else if (nickname != null && room.getNickname().equals(nickname)) { values.put(DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.STATE_OUT_DELIVERED); values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_GROUPCHAT_MESSAGE); values.put(DatabaseContract.ChatHistory.FIELD_BODY, body); } else if (nickname != null) { values.put(DatabaseContract.ChatHistory.FIELD_STATE, focusedOnRoomId != null && room.getId() == focusedOnRoomId ? DatabaseContract.ChatHistory.STATE_INCOMING : DatabaseContract.ChatHistory.STATE_INCOMING_UNREAD); values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_GROUPCHAT_MESSAGE); values.put(DatabaseContract.ChatHistory.FIELD_BODY, body); notify = true; } else { values.put(DatabaseContract.ChatHistory.FIELD_STATE, focusedOnRoomId != null && room.getId() == focusedOnRoomId ? DatabaseContract.ChatHistory.STATE_INCOMING : DatabaseContract.ChatHistory.STATE_INCOMING_UNREAD); values.put(DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.ITEM_TYPE_ERROR); values.put(DatabaseContract.ChatHistory.FIELD_BODY, body); } values.put(DatabaseContract.ChatHistory.FIELD_ACCOUNT, sessionObject.getUserBareJid().toString()); Uri uri = Uri.parse(ChatProvider.MUC_HISTORY_URI + "/" + sessionObject.getUserBareJid() + "/" + Uri.encode(room.getRoomJid().toString())); Uri x = getContentResolver().insert(uri, values); if (notify && x != null) sendNotification(sessionObject, room, msg); // if (activeChatJid == null || // !activeChatJid.getBareJid().equals(room.getRoomJid())) { // if // (body.toLowerCase().contains(room.getNickname().toLowerCase())) // { // notificationHelper.notifyNewMucMessage(sessionObject, msg); // } // } } catch (Exception ex) { Log.e(TAG, "Exception handling received MUC message", ex); } } @Override public void onPresenceError(SessionObject sessionObject, Room room, Presence presence, String nickname) { Intent intent = new Intent(); // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // intent.setAction(MainActivity.ERROR_ACTION); intent.putExtra("account", sessionObject.getUserBareJid().toString()); intent.putExtra("jid", "" + room.getRoomJid().toString()); intent.putExtra("type", "muc"); try { XMPPException.ErrorCondition c = presence.getErrorCondition(); if (c != null) { intent.putExtra("errorCondition", c.name()); intent.putExtra("errorMessage", c.name()); } else { intent.putExtra("errorCondition", "-"); intent.putExtra("errorMessage", "-"); } } catch (XMLException ex) { ex.printStackTrace(); } // if (focused) { // intent.setAction(ERROR_MESSAGE); // sendBroadcast(intent); // } else { // intent.setClass(getApplicationContext(), MainActivity.class); // notificationHelper.showMucError(room.getRoomJid().toString(), // intent); // } } @Override public void onStateChange(SessionObject sessionObject, Room room, tigase.jaxmpp.core.client.xmpp.modules.muc.Room.State oldState, tigase.jaxmpp.core.client.xmpp.modules.muc.Room.State newState) { Log.v(TAG, "room " + room.getRoomJid() + " changed state from " + oldState + " to " + newState); int state = CPresence.OFFLINE; switch (newState) { case joined: state = CPresence.ONLINE; break; default: state = CPresence.OFFLINE; } chatProvider.updateRoomState(sessionObject, room.getRoomJid(), state); } @Override public void onYouJoined(SessionObject sessionObject, Room room, String asNickname) { // TODO Auto-generated method stub Log.v(TAG, "joined room " + room.getRoomJid() + " as " + asNickname); } } private class OwnPresenceFactoryImpl implements PresenceModule.OwnPresenceStanzaFactory { @Override public Presence create(SessionObject sessionObject) { try { Presence presence = Presence.create(); SharedPreferences sharedPref = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); int defaultPresence = Long.valueOf(sharedPref.getLong("presence", CPresence.ONLINE)).intValue(); int presenceId = Long.valueOf(sharedPref.getLong("auto_presence", defaultPresence)).intValue(); Log.d(TAG, "Before presence send. defaultPresence=" + defaultPresence + "; presenceId=" + presenceId); switch (presenceId) { case CPresence.OFFLINE: presence.setType(StanzaType.unavailable); break; case CPresence.DND: presence.setShow(Presence.Show.dnd); break; case CPresence.XA: presence.setShow(Presence.Show.xa); break; case CPresence.AWAY: presence.setShow(Presence.Show.away); break; case CPresence.ONLINE: presence.setShow(Presence.Show.online); break; case CPresence.CHAT: presence.setShow(Presence.Show.chat); break; } return presence; } catch (JaxmppException e) { throw new RuntimeException(e); } } } private class PresenceHandler implements PresenceModule.ContactAvailableHandler, PresenceModule.ContactUnavailableHandler, PresenceModule.ContactChangedPresenceHandler, PresenceModule.ContactUnsubscribedHandler { private final XMPPService jaxmppService; public PresenceHandler(XMPPService jaxmppService) { this.jaxmppService = jaxmppService; } @Override public void onContactAvailable(SessionObject sessionObject, Presence stanza, JID jid, Presence.Show show, String status, Integer priority) throws JaxmppException { updateRosterItem(sessionObject, stanza); rosterProvider.updateStatus(sessionObject, jid); } @Override public void onContactChangedPresence(SessionObject sessionObject, Presence stanza, JID jid, Presence.Show show, String status, Integer priority) throws JaxmppException { updateRosterItem(sessionObject, stanza); rosterProvider.updateStatus(sessionObject, jid); } @Override public void onContactUnavailable(SessionObject sessionObject, Presence stanza, JID jid, String status) { try { updateRosterItem(sessionObject, stanza); } catch (JaxmppException ex) { Log.v(TAG, "Exception updating roster item presence", ex); } rosterProvider.updateStatus(sessionObject, jid); } @Override public void onContactUnsubscribed(SessionObject sessionObject, Presence stanza, BareJID jid) { try { updateRosterItem(sessionObject, stanza); } catch (JaxmppException ex) { Log.v(TAG, "Exception updating roster item presence", ex); } rosterProvider.updateStatus(sessionObject, JID.jidInstance(jid)); } } private class RejoinToMucRooms extends AsyncTask<Void, Void, Void> { private final SessionObject sessionObject; public RejoinToMucRooms(SessionObject sessionObject) { this.sessionObject = sessionObject; } @Override protected Void doInBackground(Void... params) { Log.i(TAG, "Rejoining to MUC Rooms. Account=" + sessionObject.getUserBareJid()); try { Jaxmpp jaxmpp = multiJaxmpp.get(sessionObject); MucModule mucModule = jaxmpp.getModule(MucModule.class); for (Room room : mucModule.getRooms()) { Log.d(TAG, "Room " + room.getRoomJid() + " is in state " + room.getState()); if (room.getState() != Room.State.joined) { Log.d(TAG, "Rejoinning to " + room.getRoomJid()); room.rejoin(); } else { (new SendUnsentGroupMessages(room)).execute(); } } } catch (JaxmppException e) { Log.e(TAG, "Exception while rejoining to rooms on connect for account " + sessionObject.getUserBareJid().toString()); } return null; } } private class ScreenStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Boolean screenOff = null; if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { screenOff = true; } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { screenOff = false; } if (screenOff != null) { sendAcks(); mobileModeFeature.setMobileMode(screenOff); } } } private class SendUnsentGroupMessages extends AsyncTask<Void, Void, Void> { private final String[] cols = new String[] { DatabaseContract.ChatHistory.FIELD_ID, DatabaseContract.ChatHistory.FIELD_ACCOUNT, DatabaseContract.ChatHistory.FIELD_AUTHOR_JID, DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.FIELD_AUTHOR_NICKNAME, DatabaseContract.ChatHistory.FIELD_BODY, DatabaseContract.ChatHistory.FIELD_DATA, DatabaseContract.ChatHistory.FIELD_JID, DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.FIELD_THREAD_ID, DatabaseContract.ChatHistory.FIELD_STANZA_ID, DatabaseContract.ChatHistory.FIELD_TIMESTAMP }; private final Room room; public SendUnsentGroupMessages(Room room) { this.room = room; } @Override protected Void doInBackground(Void... params) { Log.d("SendUnsentGroupMessages", "Sending unsent MUC " + room.getRoomJid() + " messages "); Uri u = Uri.parse(ChatProvider.UNSENT_MESSAGES_URI + "/" + room.getSessionObject().getUserBareJid()); try (Cursor c = getContentResolver().query(u, cols, DatabaseContract.ChatHistory.FIELD_ITEM_TYPE + "==? AND " + DatabaseContract.ChatHistory.FIELD_JID + "=?", new String[] { "" + DatabaseContract.ChatHistory.ITEM_TYPE_GROUPCHAT_MESSAGE, room.getRoomJid().toString() }, DatabaseContract.ChatHistory.FIELD_TIMESTAMP)) { while (c.moveToNext()) { final int id = c.getInt(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_ID)); final JID toJid = JID .jidInstance(c.getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_JID))); final String threadId = c .getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_THREAD_ID)); final String body = c.getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_BODY)); final String stanzaId = c .getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_STANZA_ID)); Log.d("SendUnsentGroupMessages", "Preparing " + id + ": " + body); JaxmppCore jaxmpp = getJaxmpp(room.getSessionObject().getUserBareJid()); if (jaxmpp.isConnected()) { try { Message msg = Message.create(); msg.setTo(toJid); msg.setType(StanzaType.groupchat); msg.setThread(threadId); msg.setBody(body); msg.setId(stanzaId); room.sendMessage(msg); ContentValues values = new ContentValues(); values.put(DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.STATE_OUT_SENT); values.put(DatabaseContract.ChatHistory.FIELD_TIMESTAMP, System.currentTimeMillis()); getContentResolver().update(Uri.parse( ChatProvider.MUC_HISTORY_URI + "/" + room.getSessionObject().getUserBareJid() + "/" + toJid.getBareJid() + "/" + id), values, null, null); } catch (JaxmppException e) { Log.w("XMPPService", "Cannot send unsent message", e); } } else { Log.w("XMPPService", "Can't find chat object for message"); } } } return null; } } private class SendUnsentMessages extends AsyncTask<Void, Void, Void> { private final String[] cols = new String[] { DatabaseContract.ChatHistory.FIELD_ID, DatabaseContract.ChatHistory.FIELD_ACCOUNT, DatabaseContract.ChatHistory.FIELD_AUTHOR_JID, DatabaseContract.ChatHistory.FIELD_ITEM_TYPE, DatabaseContract.ChatHistory.FIELD_AUTHOR_NICKNAME, DatabaseContract.ChatHistory.FIELD_BODY, DatabaseContract.ChatHistory.FIELD_DATA, DatabaseContract.ChatHistory.FIELD_JID, DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.FIELD_THREAD_ID, DatabaseContract.ChatHistory.FIELD_STANZA_ID, DatabaseContract.ChatHistory.FIELD_TIMESTAMP }; private final SessionObject sessionObject; public SendUnsentMessages(SessionObject sessionObject) { this.sessionObject = sessionObject; } @Override protected Void doInBackground(Void... params) { Uri u = Uri.parse(ChatProvider.UNSENT_MESSAGES_URI + "/" + sessionObject.getUserBareJid()); try (Cursor c = getContentResolver().query(u, cols, DatabaseContract.ChatHistory.FIELD_ITEM_TYPE + "!=?", new String[] { "" + DatabaseContract.ChatHistory.ITEM_TYPE_GROUPCHAT_MESSAGE }, DatabaseContract.ChatHistory.FIELD_TIMESTAMP)) { while (c.moveToNext()) { final int id = c.getInt(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_ID)); final JID toJid = JID .jidInstance(c.getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_JID))); final String threadId = c .getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_THREAD_ID)); final String body = c.getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_BODY)); final String stanzaId = c .getString(c.getColumnIndex(DatabaseContract.ChatHistory.FIELD_STANZA_ID)); JaxmppCore jaxmpp = getJaxmpp(sessionObject.getUserBareJid()); MessageModule messageModule = jaxmpp.getModule(MessageModule.class); if (jaxmpp.isConnected()) { try { Message msg = Message.create(); msg.setTo(toJid); msg.setType(StanzaType.chat); msg.setThread(threadId); msg.setBody(body); msg.setId(stanzaId); messageModule.sendMessage(msg); ContentValues values = new ContentValues(); values.put(DatabaseContract.ChatHistory.FIELD_STATE, DatabaseContract.ChatHistory.STATE_OUT_SENT); getContentResolver().update(Uri.parse(ChatProvider.CHAT_HISTORY_URI + "/" + sessionObject.getUserBareJid() + "/" + toJid.getBareJid() + "/" + id), values, null, null); } catch (JaxmppException e) { Log.w("XMPPService", "Cannot send unsent message", e); } } else { Log.w("XMPPService", "Can't find chat object for message"); } } } return null; } } }