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