![]() |
Hubiquitus Android
0.3
Android client for hubiquitus protocol
|
00001 /* 00002 * Copyright (c) Novedia Group 2012. 00003 * 00004 * This file is part of Hubiquitus. 00005 * 00006 * Hubiquitus is free software: you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation, either version 3 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * Hubiquitus is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with Hubiquitus. If not, see <http://www.gnu.org/licenses/>. 00018 */ 00019 00020 package org.hubiquitus.hapi.transport.xmpp; 00021 00022 import java.util.Timer; 00023 import java.util.TimerTask; 00024 00025 import org.hubiquitus.hapi.hStructures.ConnectionError; 00026 import org.hubiquitus.hapi.hStructures.ConnectionStatus; 00027 import org.hubiquitus.hapi.transport.HTransport; 00028 import org.hubiquitus.hapi.transport.HTransportDelegate; 00029 import org.hubiquitus.hapi.transport.HTransportOptions; 00030 import org.jivesoftware.smack.Connection; 00031 import org.jivesoftware.smack.ConnectionConfiguration; 00032 import org.jivesoftware.smack.ConnectionListener; 00033 import org.jivesoftware.smack.PacketListener; 00034 import org.jivesoftware.smack.SmackConfiguration; 00035 import org.jivesoftware.smack.XMPPConnection; 00036 import org.jivesoftware.smack.filter.FromContainsFilter; 00037 import org.jivesoftware.smack.filter.OrFilter; 00038 import org.jivesoftware.smack.filter.PacketFilter; 00039 import org.jivesoftware.smack.packet.Message; 00040 import org.jivesoftware.smack.packet.Packet; 00041 import org.jivesoftware.smackx.pubsub.EventElement; 00042 import org.jivesoftware.smackx.pubsub.Item; 00043 import org.jivesoftware.smackx.pubsub.ItemsExtension; 00044 import org.jivesoftware.smackx.pubsub.PayloadItem; 00045 import org.json.JSONException; 00046 import org.json.JSONObject; 00047 00048 00055 public class HTransportXMPP implements HTransport, ConnectionListener,PacketListener { 00056 00057 private HTransportDelegate callback = null; 00058 private HTransportOptions options = null; 00059 private Connection connection = null; 00060 private ConnectionConfiguration config = null; 00061 private Thread connectionThread = null; 00062 private ConnectionStatus connectionStatus = ConnectionStatus.DISCONNECTED; 00063 00064 public HTransportXMPP() { 00065 //patch for android to add xmpp providers 00066 SmackConfiguration.setPacketReplyTimeout(10000); 00067 SmackConfigureProviderManager.configureProviderManager(); 00068 }; 00069 00075 public void connect(HTransportDelegate callback, HTransportOptions options){ 00076 if (connection != null && connection.isConnected()) { 00077 connection.disconnect(); 00078 } 00079 00080 this.connectionStatus = ConnectionStatus.CONNECTING; 00081 00082 this.callback = callback; 00083 this.options = options; 00084 00085 String serverHost = options.getServerHost(); 00086 int serverPort = options.getServerPort(); 00087 String serviceName = options.getJid().getDomain(); 00088 00089 //each time it connect we create a new configuration and connection objects 00090 //because smack doesn't allow setting host, or configuration on existing objects 00091 //@todo check if config has changed rather than create a new one 00092 this.config = new ConnectionConfiguration(serverHost, serverPort, serviceName); 00093 // //patch for android to support security 00094 config.setTruststorePath("/system/etc/security/cacerts.bks"); 00095 config.setTruststoreType("bks"); 00096 00097 // Sets whether the client will use SASL authentication when logging into the server. 00098 //config.setSASLAuthenticationEnabled(true); 00099 00100 // Sets the TLS security mode used when making the connection. 00101 //config.setSecurityMode(SecurityMode.required); 00102 00103 config.setReconnectionAllowed(false); 00104 config.setSendPresence(true); 00105 00106 //add a timer to monitor connection thread. If it takes too long kill it and return conn timeout 00107 final Timer timer = new Timer(); 00108 00109 try { 00110 this.connection = new XMPPConnection(config); 00111 00112 final HTransportOptions localOptions = options; 00113 final HTransportXMPP outerClass = this; 00114 00115 //create a thread to connect async 00116 this.connectionThread = new Thread(new Runnable() { 00117 00118 public void run() { 00119 try { 00120 //launch connection and add connection listener for errors 00121 connection.connect(); 00122 connection.addConnectionListener(outerClass); 00123 try { 00124 //try to login and update status 00125 connection.login(localOptions.getUsername(), localOptions.getPassword(), localOptions.getResource()); 00126 updateStatus(ConnectionStatus.CONNECTED, ConnectionError.NO_ERROR, null); 00127 PacketFilter hserverPacketFilter = new FromContainsFilter(localOptions.getHserverService()); 00128 PacketFilter pubsubPacketFilter = new FromContainsFilter(localOptions.getPubsubService()); 00129 PacketFilter packetFilter = new OrFilter(hserverPacketFilter, pubsubPacketFilter); 00130 connection.addPacketListener(outerClass,packetFilter); 00131 } catch(Exception e) { //login failed 00132 boolean wasConnected = false; 00133 if (connection.isConnected()) { 00134 wasConnected = true; 00135 connection.disconnect(); 00136 } 00137 connection = null; 00138 00139 //update status only if connected, because if not, it's a network error not a login error 00140 if(wasConnected) { 00141 updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.AUTH_FAILED, e.getMessage()); 00142 } 00143 } 00144 00145 } catch(Exception e) { //in case connection failed 00146 connection = null; 00147 updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.TECH_ERROR , e.getMessage()); 00148 } 00149 timer.cancel(); 00150 } 00151 }); 00152 this.connectionThread.start(); //start async thread 00153 } catch(Exception e) { //in case thread creation failed or it was interrupted 00154 if (connection.isConnected()) { 00155 connection.disconnect(); 00156 } 00157 //e.printStackTrace(); 00158 this.connection = null; 00159 this.updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.TECH_ERROR, e.getMessage()); 00160 } 00161 00162 00163 //set timer task to add a connection timeout 00164 timer.schedule(new TimerTask() { 00165 00166 @Override 00167 public void run() { 00168 if (connectionThread != null && connectionThread.isAlive()) { 00169 connectionThread.interrupt(); 00170 if(connection.isConnected()) { 00171 connection.disconnect(); 00172 } 00173 } 00174 updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.CONN_TIMEOUT, null); 00175 } 00176 }, SmackConfiguration.getPacketReplyTimeout()); 00177 } 00178 00185 public void updateStatus(ConnectionStatus status, ConnectionError error, String errorMsg) { 00186 this.connectionStatus = status; 00187 if (callback != null) { 00188 callback.onStatus(status, error, errorMsg); 00189 if(this.connectionStatus == ConnectionStatus.DISCONNECTED) { 00190 callback = null; 00191 } 00192 } 00193 } 00194 00200 public void disconnect() { 00201 this.connectionStatus = ConnectionStatus.DISCONNECTING; 00202 00203 //stop connection thread if running and to try to disconnect through usual way 00204 //we should resend disconnected if connection thread is alive 00205 if(connectionThread != null && connectionThread.isAlive()) { 00206 connectionThread.interrupt(); 00207 if(connection != null && connection.isConnected()) { 00208 connection.disconnect(); 00209 } 00210 updateStatus(ConnectionStatus.DISCONNECTED, null, null); 00211 } else { 00212 try { 00213 //create a thread to disconnect async 00214 new Thread(new Runnable() { 00215 00216 public void run() { 00217 try { 00218 connection.disconnect(); 00219 updateStatus(ConnectionStatus.DISCONNECTED, null, null); 00220 } catch(Exception e) { 00221 connection = null; 00222 updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.TECH_ERROR, e.getMessage()); 00223 } 00224 } 00225 }).start(); 00226 } catch(Exception e) { 00227 this.connection = null; 00228 this.updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.TECH_ERROR, e.getMessage()); 00229 } 00230 } 00231 } 00232 00233 @Override 00234 public void sendObject(JSONObject object) { 00235 if( connectionStatus == ConnectionStatus.CONNECTED) { 00236 Message msg = new Message(options.getHserverService()); 00237 HMessageXMPP packet = new HMessageXMPP("hcommand",object.toString()); 00238 msg.addExtension(packet); 00239 connection.sendPacket(msg); 00240 } else { 00241 System.out.println("Not connected"); 00242 } 00243 } 00244 00245 @Override 00246 public void processPacket(Packet receivePacket) { 00247 if(receivePacket.getClass().equals(Message.class)) { 00248 if(receivePacket.getFrom().matches(options.getHserverService() + ".*")) { 00249 HMessageXMPP packetExtention = (HMessageXMPP)receivePacket.getExtension("hbody",""); 00250 if(packetExtention != null) { 00251 JSONObject jsonObj = null; 00252 try { 00253 jsonObj = new JSONObject(packetExtention.getContent()); 00254 } catch (JSONException e) { 00255 e.printStackTrace(); 00256 System.out.println("Received malformted JSon object from hserver in :" + this.getClass()); 00257 } 00258 if(jsonObj != null) { 00259 callback.onData(packetExtention.getType(), jsonObj); 00260 } else { 00261 System.out.println("Received malformted JSon object from hserver in :" + this.getClass()); 00262 } 00263 }else { 00264 System.out.println("erreur lors de la reception : PacketExtension erreur"); 00265 } 00266 } else if(receivePacket.getFrom().equalsIgnoreCase(options.getPubsubService())) { 00267 Message message = (Message)receivePacket; 00268 if(message.getExtension("http://jabber.org/protocol/pubsub#event") instanceof EventElement){ 00269 00270 // get the event extension 00271 EventElement event = (EventElement)message.getExtension("http://jabber.org/protocol/pubsub#event"); 00272 00273 if(event.getEventType().name().equals("items")){ // get the type of the event to check if it actually contains items 00274 // get the event 00275 ItemsExtension extension = (ItemsExtension)event.getEvent(); 00276 00277 // get the items 00278 for(int i = 0; i < extension.getItems().size(); i++){ 00279 Item item = (Item)extension.getItems().get(i); 00280 if (item instanceof PayloadItem) { 00281 00282 JSONObject jsonObj = null; 00283 try { 00284 @SuppressWarnings("unchecked") 00285 PayloadItem<HXMPPPubsubEntry> payloadItem = (PayloadItem<HXMPPPubsubEntry>)item; 00286 jsonObj = new JSONObject(payloadItem.getPayload().getContent()); 00287 } catch (JSONException e) { 00288 e.printStackTrace(); 00289 System.out.println("Received malformted JSon object from pubsub in :" + this.getClass()); 00290 System.err.println("error message :" + e.getMessage()); 00291 } 00292 if(jsonObj != null) { 00293 callback.onData("hmessage", jsonObj); 00294 } else { 00295 System.out.println("Received malformted JSon object from pubsub in :" + this.getClass()); 00296 } 00297 } 00298 } 00299 } 00300 } 00301 } 00302 } 00303 } 00304 00305 /* Connection listener interface */ 00306 @Override 00307 public void connectionClosed() { 00308 00309 } 00310 00311 @Override 00312 public void connectionClosedOnError(Exception e) { 00313 this.connectionThread.interrupt(); 00314 this.connectionThread = null; 00315 this.connection = null; 00316 this.updateStatus(ConnectionStatus.DISCONNECTED, ConnectionError.TECH_ERROR, e.getMessage()); 00317 00318 } 00319 00320 //as we use our reconnection system, this shouldn't be called 00321 @Override 00322 public void reconnectingIn(int arg0) { 00323 } 00324 00325 //as we use our reconnection system, this shouldn't be called 00326 @Override 00327 public void reconnectionFailed(Exception arg0) { 00328 } 00329 00330 //as we use our reconnection system, this shouldn't be called 00331 @Override 00332 public void reconnectionSuccessful() { 00333 } 00334 00335 00336 } 00337