// --------------------------------------------------------------------------- // jWebSocket - Copyright (c) 2010 jwebsocket.org // --------------------------------------------------------------------------- // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser 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 Lesser General Public License for // more details. // You should have received a copy of the GNU Lesser General Public License along // with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>. // --------------------------------------------------------------------------- package org.jwebsocket.android.library; import java.util.List; import java.util.Properties; import javolution.util.FastList; import org.jwebsocket.api.WebSocketClientEvent; import org.jwebsocket.api.WebSocketClientTokenListener; import org.jwebsocket.api.WebSocketPacket; import org.jwebsocket.client.token.BaseTokenClient; import org.jwebsocket.kit.RawPacket; import org.jwebsocket.kit.WebSocketException; import org.jwebsocket.token.Token; import android.app.Activity; import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.widget.Toast; /** * jWebSocket Android Service that runs locally in the same process as the * application. * * Note that this service is very local to the current thread and can be used * by only one client (activity/service) at a time. * * For more complicated background jWebSocket service follow * {@link JWSAndroidRemoteService} which allows multiple clients to use * the same service which handles multiple connections via multiple * processes. * * The sample code to bid to this service using onCreate() method of Activity: * * <pre> * private JWSLocalAndroidService mBoundService = null; * * private ServiceConnection mConnection = new ServiceConnection() { * public void onServiceConnected(ComponentName className, IBinder service) { * // This is called when the connection with the service has been * // established, giving us the service object we can use to * // interact with the service. Because we have bound to a explicit * // service that we know is running in our own process, we can * // cast its IBinder to a concrete class and directly access it. * mBoundService = ((JWSLocalAndroidService.LocalBinder)service).getService(); * * public void onServiceDisconnected(ComponentName className) { * // This is called when the connection with the service has been * // unexpectedly disconnected -- that is, its process crashed. * // Because it is running in our same process, we should never * // see this happen. * mBoundService = null; * }; * * void doBindService() { * // Establish a connection with the service. We use an explicit * // class name because we want a specific service implementation that * // we know will be running in our own process (and thus won't be * // supporting component replacement by other applications). * bindService(new Intent(Binding.this, JWSLocalAndroidService.class), mConnection, Context.BIND_AUTO_CREATE); * mIsBound = true; * } * * void doUnbindService() { * if (mIsBound) { * // Detach our existing connection. * unbindService(mConnection); * mIsBound = false; * } * } * * @Override * protected void onCreate(Bundle savedInstanceState) { * super.onCreate(savedInstanceState); * doBindService(); * * //open the connection using default conf * mBoundService.open(); * } * </pre> * @author aschulze * @author <a href="http://www.purans.net/">Puran Singh</a> */ public class JWSLocalAndroidService extends Service { /** callback events */ private final static int MT_OPENED = 0; private final static int MT_PACKET = 1; private final static int MT_CLOSED = 2; private final static int MT_TOKEN = 3; private final static String CONFIG_FILE = "jWebSocket"; private static String baseJWSUrl = "ws://jwebsocket.org:8787"; private static BaseTokenClient baseTokenClient; protected NotificationManager jwsNotification; /** * Each client activity or service can be a listener to receives event notification of jwebsocket events. */ private static List<WebSocketClientTokenListener> mListeners = new FastList<WebSocketClientTokenListener>(); private static String DEF_ENCODING = "UTF-8"; /** * Class for clients to access. Because we know this service always runs in * the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { JWSLocalAndroidService getService() { return JWSLocalAndroidService.this; } } // This is the object that receives interactions from clients. private final IBinder mBinder = new LocalBinder(); @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { jwsNotification = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // Display a notification about us starting. showStartingNotification(); baseTokenClient = new BaseTokenClient(); baseTokenClient.addListener(new Listener()); } @Override public void onDestroy() { } public void loadSettings(Activity aActivity) { Properties lProps = new Properties(); try { lProps.load(aActivity.openFileInput(CONFIG_FILE)); } catch (Exception ex) { Toast.makeText(aActivity.getApplicationContext(), ex.getClass().getSimpleName() + ":" + ex.getMessage(), Toast.LENGTH_SHORT).show(); } baseJWSUrl = (String) lProps.getProperty("url", "http://jwebsocket.org:8787/"); } public void saveSettings(Activity aActivity) { Properties lProps = new Properties(); try { lProps.put("url", baseJWSUrl); lProps.save(aActivity.openFileOutput(CONFIG_FILE, Context.MODE_PRIVATE), "jWebSocketClient Configuration"); } catch (Exception ex) { Toast.makeText(aActivity.getApplicationContext(), ex.getClass().getSimpleName() + ":" + ex.getMessage(), Toast.LENGTH_SHORT).show(); } } public void open() throws WebSocketException { baseTokenClient.open(baseJWSUrl); } public void close() throws WebSocketException { baseTokenClient.close(); } public void send(String aString) throws WebSocketException { baseTokenClient.send(baseJWSUrl, DEF_ENCODING); } public void sendToken(Token aToken) throws WebSocketException { baseTokenClient.sendToken(aToken); } public void sendText(String aTarget, String aData) throws WebSocketException { baseTokenClient.sendText(aTarget, aData); } public void broadcastText(String aData) throws WebSocketException { baseTokenClient.broadcastText(aData); } public void saveFile(byte[] aData, String aFilename, String aScope, Boolean aNotify) throws WebSocketException { baseTokenClient.saveFile(aData, aFilename, aScope, aNotify); } public void addListener(WebSocketClientTokenListener aListener) { mListeners.add(aListener); } public void removeListener(WebSocketClientTokenListener aListener) { mListeners.remove(aListener); } private Handler messageHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case MT_OPENED: notifyOpened(null); break; case MT_PACKET: notifyPacket(null, (RawPacket) message.obj); break; case MT_TOKEN: notifyToken(null, (Token) message.obj); break; case MT_CLOSED: notifyClosed(null); break; } } }; public void notifyOpened(WebSocketClientEvent aEvent) { for (WebSocketClientTokenListener lListener : mListeners) { lListener.processOpened(aEvent); } } public void notifyPacket(WebSocketClientEvent aEvent, WebSocketPacket aPacket) { for (WebSocketClientTokenListener lListener : mListeners) { lListener.processPacket(aEvent, aPacket); } } public void notifyToken(WebSocketClientEvent aEvent, Token aToken) { for (WebSocketClientTokenListener lListener : mListeners) { lListener.processToken(aEvent, aToken); } } public void notifyClosed(WebSocketClientEvent aEvent) { for (WebSocketClientTokenListener lListener : mListeners) { lListener.processClosed(aEvent); } } /** * @return the URL */ public String getURL() { return baseJWSUrl; } /** * private listener for receiving notification from the server * */ class Listener implements WebSocketClientTokenListener { public void processOpened(WebSocketClientEvent aEvent) { Message lMsg = new Message(); lMsg.what = MT_OPENED; messageHandler.sendMessage(lMsg); } public void processPacket(WebSocketClientEvent aEvent, WebSocketPacket aPacket) { Message lMsg = new Message(); lMsg.what = MT_PACKET; lMsg.obj = aPacket; messageHandler.sendMessage(lMsg); } public void processToken(WebSocketClientEvent aEvent, Token aToken) { Message lMsg = new Message(); lMsg.what = MT_TOKEN; lMsg.obj = aToken; messageHandler.sendMessage(lMsg); } public void processClosed(WebSocketClientEvent aEvent) { Message lMsg = new Message(); lMsg.what = MT_CLOSED; messageHandler.sendMessage(lMsg); } } /** * client should override this method to show notification */ public void showStartingNotification() { } }