Java tutorial
/* BEEM is a videoconference application on the Android Platform. Copyright (C) 2009 by Frederic-Charles Barthelery, Jean-Manuel Da Silva, Nikita Kozlov, Philippe Lago, Jean Baptiste Vergely, Vincent Veronis. This file is part of BEEM. BEEM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. BEEM 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 General Public License for more details. You should have received a copy of the GNU General Public License along with BEEM. If not, see <http://www.gnu.org/licenses/>. Please send bug reports with examples or suggestions to contact@beem-project.com or http://dev.beem-project.com/ Epitech, hereby disclaims all copyright interest in the program "Beem" written by Frederic-Charles Barthelery, Jean-Manuel Da Silva, Nikita Kozlov, Philippe Lago, Jean Baptiste Vergely, Vincent Veronis. Nicolas Sadirac, November 26, 2009 President of Epitech. Flavien Astraud, November 26, 2009 Head of the EIP Laboratory. */ package co.beem.project.beem; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.security.GeneralSecurityException; import java.sql.SQLException; import java.util.Date; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import javax.net.ssl.SSLContext; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.Roster.SubscriptionMode; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.SmackAndroid; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.entitycaps.EntityCapsManager; import org.jivesoftware.smackx.entitycaps.SimpleDirectoryPersistentCache; import org.jivesoftware.smackx.entitycaps.packet.CapsExtension; import org.jivesoftware.smackx.packet.ChatStateExtension; import org.jivesoftware.smackx.provider.CapsExtensionProvider; import org.jivesoftware.smackx.provider.DelayInfoProvider; import org.jivesoftware.smackx.provider.DiscoverInfoProvider; import org.jivesoftware.smackx.provider.DiscoverItemsProvider; import org.jivesoftware.smackx.pubsub.provider.EventProvider; import org.jivesoftware.smackx.pubsub.provider.ItemProvider; import org.jivesoftware.smackx.pubsub.provider.ItemsProvider; import org.jivesoftware.smackx.pubsub.provider.PubSubProvider; import org.mango.facebooktext.FacebookTextApplication; import org.mango.facebooktext.R; import org.mango.facebooktext.connection.FacebookTextConnectionListener; import org.mango.facebooktext.data.ChatMessage; import org.mango.facebooktext.data.ChatMessageType; import org.mango.facebooktext.data.ChatSession; import org.mango.facebooktext.data.User; import org.mango.facebooktext.data.UserState; import org.mango.facebooktext.database.DatabaseHelper; import org.mango.facebooktext.fbclient.AvatarFbReponseData; import org.mango.facebooktext.fbclient.MMConnector; import org.mango.facebooktext.ui.android.FbTextMainActivity; import org.mango.facebooktext.ui.android.SessionManager; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; import co.beem.project.beem.service.Contact; import co.beem.project.beem.service.Message; import co.beem.project.beem.service.XmppConnectionAdapter; import co.beem.project.beem.service.XmppFacade; import co.beem.project.beem.service.aidl.IXmppFacade; import co.beem.project.beem.service.auth.PreferenceAuthenticator; import co.beem.project.beem.smack.avatar.AvatarMetadataProvider; import co.beem.project.beem.smack.avatar.AvatarProvider; import co.beem.project.beem.smack.ping.PingExtension; import co.beem.project.beem.smack.sasl.SASLGoogleOAuth2Mechanism; import co.beem.project.beem.smack.sasl.ScramSaslMechanism; import co.beem.project.beem.utils.BeemBroadcastReceiver; import co.beem.project.beem.utils.BeemConnectivity; import co.beem.project.beem.utils.Status; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.dao.Dao; import com.j256.ormlite.stmt.UpdateBuilder; import de.duenndns.ssl.MemorizingTrustManager; /** * This class is for the Beem service. It must contains every global * informations needed to maintain the background service. The connection to the * xmpp server will be made asynchronously when the service will start. * * @author darisk */ public class FacebookTextService extends Service implements FacebookTextConnectionListener { /** The id to use for status notification. */ public static final int NOTIFICATION_STATUS_ID = 100; private static final String TAG = "FacebookTextService"; private static final int DEFAULT_XMPP_PORT = 5222; private NotificationManager mNotificationManager; private XmppConnectionAdapter mConnection; private SharedPreferences mSettings; private String mLogin; private String mHost; private String mService; private int mPort; private ConnectionConfiguration mConnectionConfiguration; private IXmppFacade.Stub mBind; private BlockingQueue<co.beem.project.beem.service.Message> savingMessageQueue; private BlockingQueue<String> loadingUserAvatarQueue; private BlockingQueue<User> stateChangeQueue; public static boolean isRunning; public static DatabaseHelper databaseHelper; private Dao<ChatMessage, Integer> chatMessageDao; private Dao<User, String> userDao; private Dao<ChatSession, Integer> chatSessionDao; private SessionManager sessionManager; private BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); private FacebookTextServiceBroadcastReceiver mOnOffReceiver = new FacebookTextServiceBroadcastReceiver(); private FacebookTextServicePreferenceListener mPreferenceListener = new FacebookTextServicePreferenceListener(); private boolean mOnOffReceiverIsRegistered; private boolean isConnected; private SSLContext sslContext; private Handler handler; private SmackAndroid smackAndroid; /** * Constructor. */ public FacebookTextService() { } /** * Initialize the connection. */ private void initConnectionConfig() { Log.d(TAG, "FacebookTextService innitiation ..."); // TODO add an option for this ? // SmackConfiguration.setPacketReplyTimeout(30000); ProxyInfo proxyInfo = getProxyConfiguration(); boolean useSystemAccount = mSettings.getBoolean(FacebookTextApplication.USE_SYSTEM_ACCOUNT_KEY, false); if (!useSystemAccount || mConnectionConfiguration == null) { /* StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);*/ SASLAuthentication.unsupportSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME); SASLAuthentication.supportSASLMechanism("PLAIN"); if (mSettings.getBoolean(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false)) mConnectionConfiguration = new ConnectionConfiguration(mHost, mPort, mService, proxyInfo); else mConnectionConfiguration = new ConnectionConfiguration(mService, proxyInfo); mConnectionConfiguration.setCallbackHandler(new PreferenceAuthenticator(this)); } if (mSettings.getBoolean("settings_key_xmpp_tls_use", false) || mSettings.getBoolean("settings_key_gmail", false)) { mConnectionConfiguration.setSecurityMode(SecurityMode.required); } if (mSettings.getBoolean(FacebookTextApplication.SMACK_DEBUG_KEY, true)) mConnectionConfiguration.setDebuggerEnabled(true); mConnectionConfiguration.setSendPresence(false); mConnectionConfiguration.setRosterLoadedAtLogin(false); // maybe not the universal path, but it works on most devices (Samsung // Galaxy, Google Nexus One) mConnectionConfiguration.setTruststoreType("BKS"); mConnectionConfiguration.setTruststorePath("/system/etc/security/cacerts.bks"); if (sslContext != null) mConnectionConfiguration.setCustomSSLContext(sslContext); } /** * Get the save proxy configuration. * * @return the proxy configuration */ private ProxyInfo getProxyConfiguration() { boolean useProxy = mSettings.getBoolean(FacebookTextApplication.PROXY_USE_KEY, false); if (useProxy) { String stype = mSettings.getString(FacebookTextApplication.PROXY_TYPE_KEY, "HTTP"); String phost = mSettings.getString(FacebookTextApplication.PROXY_SERVER_KEY, ""); String puser = mSettings.getString(FacebookTextApplication.PROXY_USERNAME_KEY, ""); String ppass = mSettings.getString(FacebookTextApplication.PROXY_PASSWORD_KEY, ""); int pport = Integer.parseInt(mSettings.getString(FacebookTextApplication.PROXY_PORT_KEY, "1080")); ProxyInfo.ProxyType type = ProxyType.valueOf(stype); return new ProxyInfo(type, phost, pport, puser, ppass); } else { return ProxyInfo.forNoProxy(); } } /** * {@inheritDoc} */ @Override public IBinder onBind(Intent intent) { Log.d(TAG, "ONBIND()"); /* * if (mConnection == null || mConnection.getAdaptee() == null || * !mConnection.getAdaptee().isConnected()) { createConnectAsync(); } if * (databaseHelper == null) getHelper(); */ return mBind; } @Override public boolean onUnbind(Intent intent) { Log.d(TAG, "ONUNBIND()"); if (mConnection != null && !mConnection.getAdaptee().isConnected()) { // this.stopSelf(); Log.d(TAG, "connection to server stopped"); } return true; } /** * {@inheritDoc} */ @Override public void onCreate() { super.onCreate(); smackAndroid = SmackAndroid.init(FacebookTextService.this); StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); savingMessageQueue = new LinkedBlockingQueue<co.beem.project.beem.service.Message>(); loadingUserAvatarQueue = new LinkedBlockingDeque<String>(); stateChangeQueue = new LinkedBlockingQueue<User>(); databaseHelper = getHelper(); try { setupDatabaseConnection(); } catch (Exception e) { e.printStackTrace(); } isRunning = true; sessionManager = new SessionManager(FacebookTextService.this); savingMessageOnBackgroundThread(new SavingNewMessageTask()); savingMessageOnBackgroundThread(new UpdateUserStateTask()); handler = new Handler(); registerReceiver(mReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); registerReceiver(mOnOffReceiver, new IntentFilter(FacebookTextApplication.GET_AVATAR)); registerReceiver(mOnOffReceiver, new IntentFilter(FacebookTextApplication.UPDATE_USER_STATE)); registerReceiver(mOnOffReceiver, new IntentFilter(FacebookTextApplication.PUSH_NOTIFICATION_FAVORITE_ONLINE)); mSettings = PreferenceManager.getDefaultSharedPreferences(this); mSettings.registerOnSharedPreferenceChangeListener(mPreferenceListener); if (mSettings.getBoolean(FacebookTextApplication.USE_AUTO_AWAY_KEY, false)) { mOnOffReceiverIsRegistered = true; registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON)); // registerReceiver(sma, filter) } String tmpJid = mSettings.getString(FacebookTextApplication.ACCOUNT_USERNAME_KEY, "").trim(); mLogin = StringUtils.parseName(tmpJid); boolean useSystemAccount = mSettings.getBoolean(FacebookTextApplication.USE_SYSTEM_ACCOUNT_KEY, false); mPort = DEFAULT_XMPP_PORT; mService = StringUtils.parseServer(tmpJid); mHost = mService; initMemorizingTrustManager(); if (mSettings.getBoolean(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false)) { mHost = mSettings.getString(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_HOST_KEY, "").trim(); if ("".equals(mHost)) mHost = mService; String tmpPort = mSettings.getString(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_PORT_KEY, "5222"); if (!"".equals(tmpPort)) mPort = Integer.parseInt(tmpPort); } if (mSettings.getBoolean(FacebookTextApplication.FULL_JID_LOGIN_KEY, false) || "gmail.com".equals(mService) || "googlemail.com".equals(mService) || useSystemAccount) { mLogin = tmpJid; } configure(ProviderManager.getInstance()); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Roster.setDefaultSubscriptionMode(SubscriptionMode.manual); mBind = new XmppFacade(this); savingMessageOnBackgroundThread(new DownloadAvatarTask()); Log.d(TAG, "Create FacebookTextService \t id: " + mLogin + " \t host: " + mHost + "\tmPort" + mPort + "\t service" + mService); } /** * {@inheritDoc} */ @Override public void onDestroy() { super.onDestroy(); isRunning = false; try { Message message = new Message(""); message.setFrom("quite_right_away"); savingMessageQueue.put(message); loadingUserAvatarQueue.add("quite_right_away"); stateChangeQueue.add(new User("quite_right_away")); // TODO set all friend state to offline } catch (InterruptedException e) { Log.v(TAG, "Could not stop the saving message queue"); e.printStackTrace(); } mNotificationManager.cancelAll(); unregisterReceiver(mReceiver); mSettings.unregisterOnSharedPreferenceChangeListener(mPreferenceListener); if (mOnOffReceiverIsRegistered) unregisterReceiver(mOnOffReceiver); if (mConnection != null && mConnection.isAuthentificated() && BeemConnectivity.isConnected(this)) mConnection.disconnect(); if (databaseHelper != null) { databaseHelper = null; OpenHelperManager.releaseHelper(); } try { smackAndroid.exit(); } catch (Exception e) { e.printStackTrace(); } Log.i(TAG, "Stopping the service"); } /** * {@inheritDoc} */ /* * @Override public void onStart(Intent intent, int startId) { * super.onStart(intent, startId); Log.d(TAG, * "onStart - where start connection"); createConnectAsync(); } */ @Override public int onStartCommand(Intent intent, int flags, int startId) { /*if (intent.getBooleanExtra(FacebookTextApplication.INTENT_NO_RESTART, false)){ return START_STICKY; }*/ Log.d(TAG, "onStart - where start connection"); if (mConnection == null || mConnection.getAdaptee() == null || !mConnection.getAdaptee().isConnected()) { createConnectAsync(); } if (databaseHelper == null) getHelper(); return START_STICKY; } /** * Create the XmppConnectionAdapter. This method makes a network request so * it must not be called on the main thread. * * @return the connection */ public XmppConnectionAdapter createConnection() { if (mConnection == null) { initConnectionConfig(); mConnection = new XmppConnectionAdapter(mConnectionConfiguration, mLogin, null, this); Log.d(TAG, "created new connection"); } return mConnection; } /* * get database helper */ public DatabaseHelper getHelper() { if (databaseHelper == null) databaseHelper = OpenHelperManager.getHelper(FacebookTextService.this, DatabaseHelper.class); return databaseHelper; } /* * Set up database connection */ public void setupDatabaseConnection() throws SQLException { userDao = databaseHelper.getUserDao(); chatMessageDao = databaseHelper.getMessageDao(); chatSessionDao = databaseHelper.getChatSessionDao(); } /** * Show a notification using the preference of the user. * * @param id * the id of the notification. * @param notif * the notification to show */ public void sendNotification(int id, Notification notif) { if (mSettings.getBoolean(FacebookTextApplication.NOTIFICATION_VIBRATE_KEY, true)) notif.defaults |= Notification.DEFAULT_VIBRATE; notif.ledARGB = 0xff0000ff; // Blue color notif.ledOnMS = 1000; notif.ledOffMS = 1000; notif.flags |= Notification.FLAG_SHOW_LIGHTS; String ringtoneStr = mSettings.getString(FacebookTextApplication.NOTIFICATION_SOUND_KEY, Settings.System.DEFAULT_NOTIFICATION_URI.toString()); notif.sound = Uri.parse(ringtoneStr); if (mSettings.getBoolean("notifications_new_message", true)) mNotificationManager.notify(id, notif); } /** * Delete a notification. * * @param id * the id of the notification */ public void deleteNotification(int id) { mNotificationManager.cancel(id); } /** * Reset the status to online after a disconnect. */ public void resetStatus() { Editor edit = mSettings.edit(); edit.putInt(FacebookTextApplication.STATUS_KEY, 1); edit.commit(); } /** * Initialize Jingle from an XmppConnectionAdapter. * * @param adaptee * XmppConnection used for jingle. */ public void initJingle(XMPPConnection adaptee) { } /** * Return a bind to an XmppFacade instance. * * @return IXmppFacade a bind to an XmppFacade instance */ public IXmppFacade getBind() { return mBind; } /** * Get the preference of the service. * * @return the preference */ public SharedPreferences getServicePreference() { return mSettings; } /** * Get the notification manager system service. * * @return the notification manager service. */ public NotificationManager getNotificationManager() { return mNotificationManager; } /** * Utility method to create and make a connection asynchronously. */ private synchronized void createConnectAsync() { if (mConnection == null) { new Thread(new Runnable() { @Override public void run() { createConnection(); connectAsync(); } }).start(); } else connectAsync(); Log.v(TAG, "starting connection"); } /** * Utility method to connect asynchronously. */ private void connectAsync() { try { mConnection.connectAsync(); } catch (RemoteException e) { Log.w(TAG, "unable to connect", e); } } /** * Get the specified Android account. * * @param accountName * the account name * @param accountType * the account type * * @return the account or null if it does not exist */ private Account getAccount(String accountName, String accountType) { AccountManager am = AccountManager.get(this); for (Account a : am.getAccountsByType(accountType)) { if (a.name.equals(accountName)) { return a; } } return null; } /** * Install the MemorizingTrustManager in the ConnectionConfiguration of * Smack. */ private void initMemorizingTrustManager() { try { sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, MemorizingTrustManager.getInstanceList(this), new java.security.SecureRandom()); } catch (GeneralSecurityException e) { Log.w(TAG, "Unable to use MemorizingTrustManager", e); } } /** * A sort of patch from this thread: * http://www.igniterealtime.org/community/thread/31118. Avoid * ClassCastException by bypassing the classloading shit of Smack. * * @param pm * The ProviderManager. */ private void configure(ProviderManager pm) { Log.d(TAG, "configure"); // Service Discovery # Items pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Privacy // pm.addIQProvider("query", "jabber:iq:privacy", new // PrivacyProvider()); // Delayed Delivery only the new version pm.addExtensionProvider("delay", "urn:xmpp:delay", new DelayInfoProvider()); // Service Discovery # Items pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Chat State ChatStateExtension.Provider chatState = new ChatStateExtension.Provider(); pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", chatState); pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", chatState); pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", chatState); pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", chatState); pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", chatState); // capabilities pm.addExtensionProvider(CapsExtension.NODE_NAME, CapsExtension.XMLNS, new CapsExtensionProvider()); // Pubsub pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub", new PubSubProvider()); pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider()); pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider()); pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub", new ItemProvider()); pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub#event", new ItemsProvider()); pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub#event", new ItemProvider()); pm.addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", new EventProvider()); // TODO rajouter les manquants pour du full pubsub // PEP avatar pm.addExtensionProvider("metadata", "urn:xmpp:avatar:metadata", new AvatarMetadataProvider()); pm.addExtensionProvider("data", "urn:xmpp:avatar:data", new AvatarProvider()); // PEPProvider pep = new PEPProvider(); // AvatarMetadataProvider avaMeta = new AvatarMetadataProvider(); // pep.registerPEPParserExtension("urn:xmpp:avatar:metadata", avaMeta); // pm.addExtensionProvider("event", // "http://jabber.org/protocol/pubsub#event", pep); // ping pm.addIQProvider(PingExtension.ELEMENT, PingExtension.NAMESPACE, PingExtension.class); /* * // Private Data Storage pm.addIQProvider("query", * "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); * // Time try { pm.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 pm.addExtensionProvider("x", "jabber:x:roster", new * RosterExchangeProvider()); // Message Events * pm.addExtensionProvider("x", "jabber:x:event", new * MessageEventProvider()); // XHTML pm.addExtensionProvider("html", * "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); * // Group Chat Invitations pm.addExtensionProvider("x", * "jabber:x:conference", new GroupChatInvitation.Provider()); // Data * Forms pm.addExtensionProvider("x", "jabber:x:data", new * DataFormProvider()); // MUC User pm.addExtensionProvider("x", * "http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC * Admin pm.addIQProvider("query", * "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); // * MUC Owner pm.addIQProvider("query", * "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); // * Version try { pm.addIQProvider("query", "jabber:iq:version", * Class.forName("org.jivesoftware.smackx.packet.Version")); } catch * (ClassNotFoundException e) { // Not sure what's happening here. * Log.w("TestClient", * "Can't load class for org.jivesoftware.smackx.packet.Version"); } // * VCard pm.addIQProvider("vCard", "vcard-temp", new VCardProvider()); * // Offline Message Requests pm.addIQProvider("offline", * "http://jabber.org/protocol/offline", new * OfflineMessageRequest.Provider()); // Offline Message Indicator * pm.addExtensionProvider("offline", * "http://jabber.org/protocol/offline", new * OfflineMessageInfo.Provider()); // Last Activity * pm.addIQProvider("query", "jabber:iq:last", new * LastActivity.Provider()); // User Search pm.addIQProvider("query", * "jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo * pm.addIQProvider("sharedgroup", * "http://www.jivesoftware.org/protocol/sharedgroup", new * SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing * pm.addExtensionProvider("addresses", * "http://jabber.org/protocol/address", new * MultipleAddressesProvider()); // FileTransfer pm.addIQProvider("si", * "http://jabber.org/protocol/si", new StreamInitiationProvider()); * pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams", * new BytestreamsProvider()); pm.addIQProvider("open", * "http://jabber.org/protocol/ibb", new IBBProviders.Open()); * pm.addIQProvider("close", "http://jabber.org/protocol/ibb", new * IBBProviders.Close()); pm.addExtensionProvider("data", * "http://jabber.org/protocol/ibb", new IBBProviders.Data()); * * pm.addIQProvider("command", COMMAND_NAMESPACE, new * AdHocCommandDataProvider()); * pm.addExtensionProvider("malformed-action", COMMAND_NAMESPACE, new * AdHocCommandDataProvider.MalformedActionError()); * pm.addExtensionProvider("bad-locale", COMMAND_NAMESPACE, new * AdHocCommandDataProvider.BadLocaleError()); * pm.addExtensionProvider("bad-payload", COMMAND_NAMESPACE, new * AdHocCommandDataProvider.BadPayloadError()); * pm.addExtensionProvider("bad-sessionid", COMMAND_NAMESPACE, new * AdHocCommandDataProvider.BadSessionIDError()); * pm.addExtensionProvider("session-expired", COMMAND_NAMESPACE, new * AdHocCommandDataProvider.SessionExpiredError()); */ /* register additionnals sasl mechanisms */ SASLAuthentication.registerSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME, SASLGoogleOAuth2Mechanism.class); SASLAuthentication.registerSASLMechanism(ScramSaslMechanism.MECHANISM_NAME, ScramSaslMechanism.class); SASLAuthentication.supportSASLMechanism(ScramSaslMechanism.MECHANISM_NAME); // Configure entity caps manager. This must be done only once File f = new File(getCacheDir(), "entityCaps"); f.mkdirs(); try { EntityCapsManager.setPersistentCache(new SimpleDirectoryPersistentCache(f)); } catch (IllegalStateException e) { Log.v(TAG, "EntityCapsManager already initialized", e); } catch (IOException e) { Log.w(TAG, "EntityCapsManager not able to reuse persistent cache"); } } /** * Listen on preference changes. */ private class FacebookTextServicePreferenceListener implements SharedPreferences.OnSharedPreferenceChangeListener { /** * ctor. */ public FacebookTextServicePreferenceListener() { } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (FacebookTextApplication.USE_AUTO_AWAY_KEY.equals(key)) { if (sharedPreferences.getBoolean(FacebookTextApplication.USE_AUTO_AWAY_KEY, false)) { mOnOffReceiverIsRegistered = true; registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON)); } else { mOnOffReceiverIsRegistered = false; unregisterReceiver(mOnOffReceiver); } } } } /** * Listen on some Intent broadcast, ScreenOn and ScreenOff. */ private class FacebookTextServiceBroadcastReceiver extends BroadcastReceiver { private String mOldStatus; private int mOldMode; /** * Constructor. */ public FacebookTextServiceBroadcastReceiver() { } @Override public void onReceive(final Context context, final Intent intent) { String intentAction = intent.getAction(); if (intentAction.equals(Intent.ACTION_SCREEN_OFF) && mConnection != null) { mOldMode = mConnection.getPreviousMode(); mOldStatus = mConnection.getPreviousStatus(); if (mConnection.isAuthentificated()) mConnection.changeStatus(Status.CONTACT_STATUS_AWAY, mSettings.getString(FacebookTextApplication.AUTO_AWAY_MSG_KEY, "Away")); } else if (FacebookTextApplication.GET_AVATAR.equals(intentAction)) { putUserToGetAvatar(intent.getStringExtra(User.USER_JABBER_ID)); } else if (FacebookTextApplication.UPDATE_USER_STATE.equals(intentAction)) { putUserStateChanged(new User(intent.getStringExtra(User.USER_JABBER_ID), intent.getIntExtra(User.USER_STATE_FIELD, 0))); } else if (FacebookTextApplication.PUSH_NOTIFICATION_FAVORITE_ONLINE.equals(intentAction)) { makeNotificationForFavorite(new User(intent.getStringExtra(User.USER_JABBER_ID), intent.getStringExtra(User.USER_NAME_FIELD), "")); } else if (intentAction.equals(Intent.ACTION_SCREEN_ON) && mConnection != null) { if (mConnection.isAuthentificated()) mConnection.changeStatus(mOldMode, mOldStatus); } } } // private class SavingNewMessageTask implements Runnable { @Override public void run() { outter: while (isRunning) try { co.beem.project.beem.service.Message newMessage = savingMessageQueue.take(); String jId = newMessage.getFrom(); if (jId.equalsIgnoreCase("quite_right_away")) break outter; try { User user = userDao.queryForId(jId); ChatSession thisSession = null; List<ChatSession> listSessionsWithThisFriend = null; try { listSessionsWithThisFriend = chatSessionDao.query(chatSessionDao.queryBuilder().where() .eq(ChatSession.USER_NAME_FIELD, user).prepare()); } catch (SQLException e) { e.printStackTrace(); } /* * if there no session chat exists, this is the first * time chat with this contact We create new one */ if (listSessionsWithThisFriend == null || listSessionsWithThisFriend.size() == 0) { thisSession = new ChatSession(user); try { chatSessionDao.create(thisSession); } catch (SQLException e) { e.printStackTrace(); } } // else take the first session found with this friend else { thisSession = listSessionsWithThisFriend.get(0); } ChatMessage chatMessage = null; //check image message if (newMessage.getBody().contains("fbcdn-sphotos")) { for (String imageLink : newMessage.getBody().split("\\s+")) { if (imageLink.contains("fbcdn-sphotos-h-a.akamai")) { ChatMessage event = new ChatMessage("", new Date().getTime(), user, ChatMessageType.image, false); event.setSession(thisSession); event.setImagePath(imageLink); chatMessageDao.create(event); thisSession.setLastMessage(event); user.increaseMEssageCount(); } } chatSessionDao.update(thisSession); } else { // text message chatMessage = new ChatMessage(newMessage.getBody(), newMessage.getTimestamp().getTime(), user, ChatMessageType.text, false); chatMessage.setSession(thisSession); chatMessageDao.create(chatMessage); thisSession.setLastMessage(chatMessage); chatSessionDao.update(thisSession); user.increaseMEssageCount(); } //update message count for user UpdateBuilder<User, String> updateBuilder = userDao.updateBuilder(); updateBuilder.updateColumnValue(User.USER_MESSAGE_COUNT_FIELD, user.getMessageCount()); updateBuilder.where().eq(User.USER_JABBER_ID, user.getJabberId()); } catch (SQLException e) { e.printStackTrace(); } // find the session for this message first } catch (InterruptedException e) { Log.v(TAG, "Could not save this new message"); e.printStackTrace(); } Log.v(TAG, "SavingNewMessageTask is closed"); } } /* * * * */ public class DownloadAvatarTask implements Runnable { @Override public void run() { try { Thread.sleep(20000); } catch (Exception e) { e.printStackTrace(); } String myUid = PreferenceManager.getDefaultSharedPreferences(FacebookTextService.this) .getString(FacebookTextApplication.ACCOUNT_USERNAME_KEY, ""); if (myUid != null) { loadingUserAvatarQueue.add("-" + myUid); } try { // load user list Log.v(TAG, "Task download avatar start"); outer: while (isRunning) { String userId = loadingUserAvatarQueue.take(); // set up connector and download avatar for this user MMConnector connector = new MMConnector(); try { if (mSettings.getBoolean(FacebookTextApplication.LOAD_AVATAR_KEY, true)) { String id = userId.substring(1).split("@")[0]; // check file downloaded or not File f = getFileStreamPath(id + ".png"); if (f.length() > 2000) { throw new Exception("file downloaded"); } if (userId.equals("quite_right_away")) break outer; AvatarFbReponseData responseData = connector.sendRequest(id); if (responseData != null && responseData.data != null) { // start to down load InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; try { URL url = new URL(responseData.data.url); connection = (HttpURLConnection) url.openConnection(); connection.connect(); // expect HTTP 200 OK, so we don't // mistakenly save error report // instead of the file if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { Log.w(TAG, "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage()); throw new Exception("cannot download avatar for this user"); } // this will be useful to display download // percentage // might be -1: server did not report the // length int fileLength = connection.getContentLength(); // download the file output = openFileOutput(id + ".png", MODE_PRIVATE); input = connection.getInputStream(); Bitmap bmp = BitmapFactory.decodeStream(input); bmp.compress(Bitmap.CompressFormat.PNG, 100, output); // 100-best quality /* * byte data[] = new byte[4096]; long total * = 0; int count; while ((count = * input.read(data)) != -1) { total += * count; output.write(data, 0, count); } */ } catch (Exception e) { e.printStackTrace(); } finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException ignored) { } if (connection != null) connection.disconnect(); } } Thread.sleep(100); } } catch (Exception e) { e.printStackTrace(); } // ending of task downloading for one user } Log.v(TAG, "DownloadAvatarTask is closed"); } catch (Exception e) { e.printStackTrace(); } } } /* * * Change user state in database */ public class UpdateUserStateTask implements Runnable { @Override public void run() { outer: while (isRunning) { try { User user = stateChangeQueue.take(); UserState state = user.getState(); if (user.getJabberId().equals("quite_right_away")) break outer; UpdateBuilder<User, String> builder = userDao.updateBuilder(); builder.updateColumnValue(User.USER_STATE_FIELD, user.getState()); builder.where().eq(User.USER_JABBER_ID, user.getJabberId()); int affterRows = userDao.update(builder.prepare()); Log.v(TAG, "update affted " + affterRows + " rows: " + user.getJabberId()); user = userDao.queryForId(user.getJabberId()); String currentId = mSettings.getString(FacebookTextApplication.CURRENT_CHAT_ID, ""); if (user != null && user.isFavorite() && state == UserState.available && !user.getJabberId().equals(currentId)) { // push notification here makeNotificationForFavorite(user); toastMessage(user.getName() + " is now online"); } } catch (InterruptedException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } // end of endless while Log.v(TAG, "UpdateUserStateTask is closed"); } } public boolean putMessageToSave(co.beem.project.beem.service.Message message) { if (message == null) return false; return savingMessageQueue.add(message); } public boolean putUserToGetAvatar(String userId) { if (userId == null) return false; else return loadingUserAvatarQueue.add(userId); } public boolean putUserStateChanged(User user) { if (user == null) return false; else return stateChangeQueue.add(user); } /* * Execute thread to retrieve message when available this may be hard work * for mobile * */ public static Thread savingMessageOnBackgroundThread(final Runnable runnable) { final Thread t = new Thread() { @Override public void run() { try { runnable.run(); } finally { } } }; t.start(); return t; } public void toastMessage(final String message) { handler.post(new Runnable() { @Override public void run() { Toast.makeText(FacebookTextService.this, message, Toast.LENGTH_SHORT).show(); ; } }); } public void makeNotificationForFavorite(User user) { NotificationCompat.Builder notif = new NotificationCompat.Builder(FacebookTextService.this); try { String contactJid = user.getJabberId(); Contact c = getBind().getRoster().getContact(contactJid); String contactName = contactJid; if (c != null) { contactName = c.getName(); Bitmap avatar = getAvatar(c); notif.setLargeIcon(avatar); } notif.setTicker(contactName).setContentTitle(contactName); notif.setContentText(getString(R.string.is_now_online)); notif.setSmallIcon(R.drawable.ic_stat_ic_launcher_fbtext); notif.setContentIntent(makeChatIntent(user)); notif.setAutoCancel(true).setWhen(System.currentTimeMillis()); sendNotification(user.getJabberId().hashCode(), notif.getNotification()); } catch (RemoteException e) { Log.e(TAG, e.getMessage()); } } private PendingIntent makeChatIntent(User user) { Intent chatIntent = new Intent(FacebookTextService.this, FbTextMainActivity.class); chatIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); try { chatIntent.setData(user.toUri()); } catch (Exception e) { Log.e(TAG, e.getMessage()); } PendingIntent contentIntent = PendingIntent.getActivity(FacebookTextService.this, 0, chatIntent, PendingIntent.FLAG_UPDATE_CURRENT); return contentIntent; } /** * Get the avatar of a contact. * * @param c * the contact * @return the avatar of c or null if avatar is not defined */ private Bitmap getAvatar(Contact c) { String id = c.getAvatarId(); if (id == null) id = ""; File internalFile = getFileStreamPath(c.getJID().substring(1).split("@")[0] + ".png"); Uri uri = Uri.fromFile(internalFile); try { InputStream in = getContentResolver().openInputStream(uri); return BitmapFactory.decodeStream(in); } catch (FileNotFoundException e) { Log.d(TAG, "Error loading avatar id: " + id, e); return null; } } public void onConnected() { } public void onDisconnect() { } public void onConnecting() { } public void onNoInternetConnection() { } @Override public void onConnectionError() { // TODO Auto-generated method stub } }