001/** 002 * 003 * Copyright 2003-2007 Jive Software. 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.smack.packet; 019 020import java.util.Locale; 021 022import org.jivesoftware.smack.packet.id.StanzaIdUtil; 023import org.jivesoftware.smack.util.Objects; 024import org.jivesoftware.smack.util.StringUtils; 025import org.jivesoftware.smack.util.TypedCloneable; 026import org.jivesoftware.smack.util.XmlStringBuilder; 027 028import org.jxmpp.jid.Jid; 029 030/** 031 * Represents XMPP presence packets. Every presence stanza has a type, which is one of 032 * the following values: 033 * <ul> 034 * <li>{@link Presence.Type#available available} -- (Default) indicates the user is available to 035 * receive messages. 036 * <li>{@link Presence.Type#unavailable unavailable} -- the user is unavailable to receive messages. 037 * <li>{@link Presence.Type#subscribe subscribe} -- request subscription to recipient's presence. 038 * <li>{@link Presence.Type#subscribed subscribed} -- grant subscription to sender's presence. 039 * <li>{@link Presence.Type#unsubscribe unsubscribe} -- request removal of subscription to 040 * sender's presence. 041 * <li>{@link Presence.Type#unsubscribed unsubscribed} -- grant removal of subscription to 042 * sender's presence. 043 * <li>{@link Presence.Type#error error} -- the presence stanza contains an error message. 044 * </ul><p> 045 * 046 * A number of attributes are optional: 047 * <ul> 048 * <li>Status -- free-form text describing a user's presence (i.e., gone to lunch). 049 * <li>Priority -- non-negative numerical priority of a sender's resource. The 050 * highest resource priority is the default recipient of packets not addressed 051 * to a particular resource. 052 * <li>Mode -- one of five presence modes: {@link Mode#available available} (the default), 053 * {@link Mode#chat chat}, {@link Mode#away away}, {@link Mode#xa xa} (extended away), and 054 * {@link Mode#dnd dnd} (do not disturb). 055 * </ul><p> 056 * 057 * Presence packets are used for two purposes. First, to notify the server of 058 * the user's current presence status. Second, they are used to subscribe and 059 * unsubscribe users from the roster. 060 * 061 * @author Matt Tucker 062 */ 063public final class Presence extends Stanza implements TypedCloneable<Presence> { 064 065 public static final String ELEMENT = "presence"; 066 067 private Type type = Type.available; 068 private String status = null; 069 070 /** 071 * The priority of the presence. The magic value {@link Integer#MIN_VALUE} is used to indicate that the original 072 * presence stanza did not had an explicit priority set. In which case the priority defaults to 0. 073 * 074 * @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3.</a> 075 */ 076 private int priority = Integer.MIN_VALUE; 077 078 private Mode mode = null; 079 080 /** 081 * Creates a new presence update. Status, priority, and mode are left un-set. 082 * 083 * @param type the type. 084 */ 085 public Presence(Type type) { 086 // Ensure that the stanza ID is set by calling super(). 087 super(); 088 setType(type); 089 } 090 091 /** 092 * Creates a new presence with the given type and using the given XMPP address as recipient. 093 * 094 * @param to the recipient. 095 * @param type the type. 096 * @since 4.2 097 */ 098 public Presence(Jid to, Type type) { 099 this(type); 100 setTo(to); 101 } 102 103 /** 104 * Creates a new presence update with a specified status, priority, and mode. 105 * 106 * @param type the type. 107 * @param status a text message describing the presence update. 108 * @param priority the priority of this presence update. 109 * @param mode the mode type for this presence update. 110 */ 111 public Presence(Type type, String status, int priority, Mode mode) { 112 // Ensure that the stanza ID is set by calling super(). 113 super(); 114 setType(type); 115 setStatus(status); 116 setPriority(priority); 117 setMode(mode); 118 } 119 120 /** 121 * Copy constructor. 122 * <p> 123 * This does not perform a deep clone, as extension elements are shared between the new and old 124 * instance. 125 * </p> 126 * 127 * @param other 128 */ 129 public Presence(Presence other) { 130 super(other); 131 this.type = other.type; 132 this.status = other.status; 133 this.priority = other.priority; 134 this.mode = other.mode; 135 } 136 137 /** 138 * Returns true if the {@link Type presence type} is available (online) and 139 * false if the user is unavailable (offline), or if this is a presence packet 140 * involved in a subscription operation. This is a convenience method 141 * equivalent to <tt>getType() == Presence.Type.available</tt>. Note that even 142 * when the user is available, their presence mode may be {@link Mode#away away}, 143 * {@link Mode#xa extended away} or {@link Mode#dnd do not disturb}. Use 144 * {@link #isAway()} to determine if the user is away. 145 * 146 * @return true if the presence type is available. 147 */ 148 public boolean isAvailable() { 149 return type == Type.available; 150 } 151 152 /** 153 * Returns true if the presence type is {@link Type#available available} and the presence 154 * mode is {@link Mode#away away}, {@link Mode#xa extended away}, or 155 * {@link Mode#dnd do not disturb}. False will be returned when the type or mode 156 * is any other value, including when the presence type is unavailable (offline). 157 * This is a convenience method equivalent to 158 * <tt>type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd)</tt>. 159 * 160 * @return true if the presence type is available and the presence mode is away, xa, or dnd. 161 */ 162 public boolean isAway() { 163 return type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd); 164 } 165 166 /** 167 * Returns the type of this presence packet. 168 * 169 * @return the type of the presence packet. 170 */ 171 public Type getType() { 172 return type; 173 } 174 175 /** 176 * Sets the type of the presence packet. 177 * 178 * @param type the type of the presence packet. 179 */ 180 public void setType(Type type) { 181 this.type = Objects.requireNonNull(type, "Type cannot be null"); 182 } 183 184 /** 185 * Returns the status message of the presence update, or <tt>null</tt> if there 186 * is not a status. The status is free-form text describing a user's presence 187 * (i.e., "gone to lunch"). 188 * 189 * @return the status message. 190 */ 191 public String getStatus() { 192 return status; 193 } 194 195 /** 196 * Sets the status message of the presence update. The status is free-form text 197 * describing a user's presence (i.e., "gone to lunch"). 198 * 199 * @param status the status message. 200 */ 201 public void setStatus(String status) { 202 this.status = status; 203 } 204 205 /** 206 * Returns the priority of the presence, or Integer.MIN_VALUE if no priority has been set. 207 * 208 * @return the priority. 209 * @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a> 210 */ 211 public int getPriority() { 212 if (priority == Integer.MIN_VALUE) { 213 return 0; 214 } 215 return priority; 216 } 217 218 /** 219 * Sets the priority of the presence. The valid range is -128 through 127. 220 * 221 * @param priority the priority of the presence. 222 * @throws IllegalArgumentException if the priority is outside the valid range. 223 * @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a> 224 */ 225 public void setPriority(int priority) { 226 if (priority < -128 || priority > 127) { 227 throw new IllegalArgumentException("Priority value " + priority + 228 " is not valid. Valid range is -128 through 127."); 229 } 230 this.priority = priority; 231 } 232 233 /** 234 * Returns the mode of the presence update. 235 * 236 * @return the mode. 237 */ 238 public Mode getMode() { 239 if (mode == null) { 240 return Mode.available; 241 } 242 return mode; 243 } 244 245 /** 246 * Sets the mode of the presence update. A null presence mode value is interpreted 247 * to be the same thing as {@link Presence.Mode#available}. 248 * 249 * @param mode the mode. 250 */ 251 public void setMode(Mode mode) { 252 this.mode = mode; 253 } 254 255 @Override 256 public String toString() { 257 StringBuilder sb = new StringBuilder(); 258 sb.append("Presence Stanza ["); 259 logCommonAttributes(sb); 260 sb.append("type=").append(type).append(','); 261 if (mode != null) { 262 sb.append("mode=").append(mode).append(','); 263 } 264 if (!StringUtils.isNullOrEmpty(status)) { 265 sb.append("status=").append(status).append(','); 266 } 267 if (priority != Integer.MIN_VALUE) { 268 sb.append("prio=").append(priority).append(','); 269 } 270 sb.append(']'); 271 return sb.toString(); 272 } 273 274 @Override 275 public XmlStringBuilder toXML(String enclosingNamespace) { 276 XmlStringBuilder buf = new XmlStringBuilder(enclosingNamespace); 277 buf.halfOpenElement(ELEMENT); 278 addCommonAttributes(buf, enclosingNamespace); 279 if (type != Type.available) { 280 buf.attribute("type", type); 281 } 282 buf.rightAngleBracket(); 283 284 buf.optElement("status", status); 285 if (priority != Integer.MIN_VALUE) { 286 buf.element("priority", Integer.toString(priority)); 287 } 288 if (mode != null && mode != Mode.available) { 289 buf.element("show", mode); 290 } 291 292 buf.append(getExtensions(), enclosingNamespace); 293 294 // Add the error sub-packet, if there is one. 295 appendErrorIfExists(buf, enclosingNamespace); 296 297 buf.closeElement(ELEMENT); 298 299 return buf; 300 } 301 302 /** 303 * Creates and returns a copy of this presence stanza. 304 * <p> 305 * This does not perform a deep clone, as extension elements are shared between the new and old 306 * instance. 307 * </p> 308 * @return a clone of this presence. 309 */ 310 @Override 311 public Presence clone() { 312 return new Presence(this); 313 } 314 315 /** 316 * Clone this presence and set a newly generated stanza ID as the clone's ID. 317 * 318 * @return a "clone" of this presence with a different stanza ID. 319 * @since 4.1.2 320 */ 321 public Presence cloneWithNewId() { 322 Presence clone = clone(); 323 clone.setStanzaId(StanzaIdUtil.newStanzaId()); 324 return clone; 325 } 326 327 /** 328 * An enum to represent the presence type. Note that presence type is often confused 329 * with presence mode. Generally, if a user is signed in to a server, they have a presence 330 * type of {@link #available available}, even if the mode is {@link Mode#away away}, 331 * {@link Mode#dnd dnd}, etc. The presence type is only {@link #unavailable unavailable} when 332 * the user is signing out of the server. 333 */ 334 public enum Type { 335 336 /** 337 * The user is available to receive messages (default). 338 */ 339 available, 340 341 /** 342 * The user is unavailable to receive messages. 343 */ 344 unavailable, 345 346 /** 347 * Request subscription to recipient's presence. 348 */ 349 subscribe, 350 351 /** 352 * Grant subscription to sender's presence. 353 */ 354 subscribed, 355 356 /** 357 * Request removal of subscription to sender's presence. 358 */ 359 unsubscribe, 360 361 /** 362 * Grant removal of subscription to sender's presence. 363 */ 364 unsubscribed, 365 366 /** 367 * The presence stanza contains an error message. 368 */ 369 error, 370 371 /** 372 * A presence probe as defined in section 4.3 of RFC 6121. 373 */ 374 probe, 375 ; 376 377 /** 378 * Converts a String into the corresponding types. Valid String values that can be converted 379 * to types are: "available", "unavailable", "subscribe", "subscribed", "unsubscribe", 380 * "unsubscribed" and "error". 381 * 382 * @param string the String value to covert. 383 * @return the corresponding Type. 384 * @throws IllegalArgumentException when not able to parse the string parameter 385 * @throws NullPointerException if the string is null 386 */ 387 public static Type fromString(String string) { 388 return Type.valueOf(string.toLowerCase(Locale.US)); 389 } 390 } 391 392 /** 393 * An enum to represent the presence mode. 394 */ 395 public enum Mode { 396 397 /** 398 * Free to chat. 399 */ 400 chat, 401 402 /** 403 * Available (the default). 404 */ 405 available, 406 407 /** 408 * Away. 409 */ 410 away, 411 412 /** 413 * Away for an extended period of time. 414 */ 415 xa, 416 417 /** 418 * Do not disturb. 419 */ 420 dnd; 421 422 /** 423 * Converts a String into the corresponding types. Valid String values that can be converted 424 * to types are: "chat", "available", "away", "xa", and "dnd". 425 * 426 * @param string the String value to covert. 427 * @return the corresponding Type. 428 * @throws IllegalArgumentException when not able to parse the string parameter 429 * @throws NullPointerException if the string is null 430 */ 431 public static Mode fromString(String string) { 432 return Mode.valueOf(string.toLowerCase(Locale.US)); 433 } 434 } 435}