001/** 002 * 003 * Copyright 2003-2006 Jive Software, 2014 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.iqlast; 019 020import java.util.Map; 021import java.util.WeakHashMap; 022 023import org.jivesoftware.smack.SmackException.NoResponseException; 024import org.jivesoftware.smack.SmackException.NotConnectedException; 025import org.jivesoftware.smack.XMPPConnection; 026import org.jivesoftware.smack.ConnectionCreationListener; 027import org.jivesoftware.smack.PacketListener; 028import org.jivesoftware.smack.Manager; 029import org.jivesoftware.smack.XMPPException.XMPPErrorException; 030import org.jivesoftware.smack.filter.AndFilter; 031import org.jivesoftware.smack.filter.IQTypeFilter; 032import org.jivesoftware.smack.filter.PacketFilter; 033import org.jivesoftware.smack.filter.PacketTypeFilter; 034import org.jivesoftware.smack.packet.IQ; 035import org.jivesoftware.smack.packet.Message; 036import org.jivesoftware.smack.packet.Packet; 037import org.jivesoftware.smack.packet.Presence; 038import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 039import org.jivesoftware.smackx.iqlast.packet.LastActivity; 040 041/** 042 * A last activity manager for handling information about the last activity 043 * associated with a Jabber ID. A manager handles incoming LastActivity requests 044 * of existing Connections. It also allows to request last activity information 045 * of other users. 046 * <p> 047 * 048 * LastActivity (XEP-0012) based on the sending JID's type allows for retrieval 049 * of: 050 * <ol> 051 * <li>How long a particular user has been idle 052 * <li>How long a particular user has been logged-out and the message the 053 * specified when doing so. 054 * <li>How long a host has been up. 055 * </ol> 056 * <p/> 057 * 058 * For example to get the idle time of a user logged in a resource, simple send 059 * the LastActivity packet to them, as in the following code: 060 * <p> 061 * 062 * <pre> 063 * XMPPConnection con = new XMPPTCPConnection("jabber.org"); 064 * con.login("john", "doe"); 065 * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org/Smack"); 066 * </pre> 067 * 068 * To get the lapsed time since the last user logout is the same as above but 069 * with out the resource: 070 * 071 * <pre> 072 * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org"); 073 * </pre> 074 * 075 * To get the uptime of a host, you simple send the LastActivity packet to it, 076 * as in the following code example: 077 * <p> 078 * 079 * <pre> 080 * LastActivity activity = LastActivity.getLastActivity(con, "jabber.org"); 081 * </pre> 082 * 083 * @author Gabriel Guardincerri 084 * @author Florian Schmaus 085 * @see <a href="http://xmpp.org/extensions/xep-0012.html">XEP-0012: Last 086 * Activity</a> 087 */ 088 089public class LastActivityManager extends Manager { 090 private static final Map<XMPPConnection, LastActivityManager> instances = new WeakHashMap<XMPPConnection, LastActivityManager>(); 091 private static final PacketFilter IQ_GET_LAST_FILTER = new AndFilter(new IQTypeFilter( 092 IQ.Type.GET), new PacketTypeFilter(LastActivity.class)); 093 094 private static boolean enabledPerDefault = true; 095 096 /** 097 * Enable or disable Last Activity for new XMPPConnections. 098 * 099 * @param enabledPerDefault 100 */ 101 public static void setEnabledPerDefault(boolean enabledPerDefault) { 102 LastActivityManager.enabledPerDefault = enabledPerDefault; 103 } 104 105 // Enable the LastActivity support on every established connection 106 static { 107 XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() { 108 public void connectionCreated(XMPPConnection connection) { 109 LastActivityManager.getInstanceFor(connection); 110 } 111 }); 112 } 113 114 public static synchronized LastActivityManager getInstanceFor(XMPPConnection connection) { 115 LastActivityManager lastActivityManager = instances.get(connection); 116 if (lastActivityManager == null) 117 lastActivityManager = new LastActivityManager(connection); 118 return lastActivityManager; 119 } 120 121 private volatile long lastMessageSent; 122 private boolean enabled = false; 123 124 /** 125 * Creates a last activity manager to response last activity requests. 126 * 127 * @param connection 128 * The XMPPConnection that the last activity requests will use. 129 */ 130 private LastActivityManager(XMPPConnection connection) { 131 super(connection); 132 133 // Listen to all the sent messages to reset the idle time on each one 134 connection.addPacketSendingListener(new PacketListener() { 135 public void processPacket(Packet packet) { 136 Presence presence = (Presence) packet; 137 Presence.Mode mode = presence.getMode(); 138 if (mode == null) return; 139 switch (mode) { 140 case available: 141 case chat: 142 // We assume that only a switch to available and chat indicates user activity 143 // since other mode changes could be also a result of some sort of automatism 144 resetIdleTime(); 145 default: 146 break; 147 } 148 } 149 }, PacketTypeFilter.PRESENCE); 150 151 connection.addPacketSendingListener(new PacketListener() { 152 @Override 153 public void processPacket(Packet packet) { 154 Message message = (Message) packet; 155 // if it's not an error message, reset the idle time 156 if (message.getType() == Message.Type.error) return; 157 resetIdleTime(); 158 } 159 }, PacketTypeFilter.MESSAGE); 160 161 // Register a listener for a last activity query 162 connection.addPacketListener(new PacketListener() { 163 164 public void processPacket(Packet packet) throws NotConnectedException { 165 if (!enabled) return; 166 LastActivity message = new LastActivity(); 167 message.setType(IQ.Type.RESULT); 168 message.setTo(packet.getFrom()); 169 message.setFrom(packet.getTo()); 170 message.setPacketID(packet.getPacketID()); 171 message.setLastActivity(getIdleTime()); 172 173 connection().sendPacket(message); 174 } 175 176 }, IQ_GET_LAST_FILTER); 177 178 if (enabledPerDefault) { 179 enable(); 180 } 181 resetIdleTime(); 182 instances.put(connection, this); 183 } 184 185 public synchronized void enable() { 186 ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(LastActivity.NAMESPACE); 187 enabled = true; 188 } 189 190 public synchronized void disable() { 191 ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(LastActivity.NAMESPACE); 192 enabled = false; 193 } 194 195 /** 196 * Resets the idle time to 0, this should be invoked when a new message is 197 * sent. 198 */ 199 private void resetIdleTime() { 200 lastMessageSent = System.currentTimeMillis(); 201 } 202 203 /** 204 * The idle time is the lapsed time between the last message sent and now. 205 * 206 * @return the lapsed time between the last message sent and now. 207 */ 208 private long getIdleTime() { 209 long lms = lastMessageSent; 210 long now = System.currentTimeMillis(); 211 return ((now - lms) / 1000); 212 } 213 214 /** 215 * Returns the last activity of a particular jid. If the jid is a full JID 216 * (i.e., a JID of the form of 'user@host/resource') then the last activity 217 * is the idle time of that connected resource. On the other hand, when the 218 * jid is a bare JID (e.g. 'user@host') then the last activity is the lapsed 219 * time since the last logout or 0 if the user is currently logged in. 220 * Moreover, when the jid is a server or component (e.g., a JID of the form 221 * 'host') the last activity is the uptime. 222 * 223 * @param jid 224 * the JID of the user. 225 * @return the LastActivity packet of the jid. 226 * @throws XMPPErrorException 227 * thrown if a server error has occured. 228 * @throws NoResponseException if there was no response from the server. 229 * @throws NotConnectedException 230 */ 231 public LastActivity getLastActivity(String jid) throws NoResponseException, XMPPErrorException, 232 NotConnectedException { 233 LastActivity activity = new LastActivity(jid); 234 return (LastActivity) connection().createPacketCollectorAndSend(activity).nextResultOrThrow(); 235 } 236 237 /** 238 * Returns true if Last Activity (XEP-0012) is supported by a given JID 239 * 240 * @param jid a JID to be tested for Last Activity support 241 * @return true if Last Activity is supported, otherwise false 242 * @throws NotConnectedException 243 * @throws XMPPErrorException 244 * @throws NoResponseException 245 */ 246 public boolean isLastActivitySupported(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException { 247 return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid, LastActivity.NAMESPACE); 248 } 249}